1use std::{
8 collections::VecDeque, fmt::Formatter, future::Future, hash::Hash, net::SocketAddr, num::NonZeroUsize,
9 str::FromStr, sync::Arc,
10};
11
12use anyhow::anyhow;
13use base64::Engine;
14use bytesize::ByteSize;
15use dashmap::DashMap;
16use futures::{
17 FutureExt, StreamExt, TryStreamExt,
18 future::{AbortHandle, AbortRegistration},
19};
20use hopr_api::{
21 chain::HoprChainApi,
22 graph::{
23 NetworkGraphTraverse, NetworkGraphUpdate, NetworkGraphView, NetworkGraphWrite,
24 traits::{EdgeObservableRead, EdgeObservableWrite},
25 },
26 network::NetworkStreamControl,
27};
28#[cfg(not(feature = "explicit-path"))]
29use hopr_lib::HopRouting;
30#[cfg(feature = "explicit-path")]
31use hopr_lib::HoprSessionClientExplicitPathConfig;
32#[cfg(feature = "explicit-path")]
33use hopr_lib::api::types::internal::routing::RoutingOptions;
34use hopr_lib::{
35 Hopr, HoprSessionClientConfig,
36 api::{network::NetworkView, node::HoprSessionClientOperations, types::primitive::prelude::Address},
37 errors::HoprLibError,
38 exports::transport::{
39 HoprSession, HoprSessionConfigurator, OffchainPublicKey, SURB_SIZE, ServiceId, SessionId, SessionTarget,
40 transfer_session,
41 },
42};
43use hopr_utils::{
44 network_types::{
45 prelude::{ConnectedUdpStream, IpOrHost, IpProtocol, SealedHost, UdpStreamParallelism},
46 udp::ForeignDataMode,
47 },
48 runtime::Abortable,
49};
50use human_bandwidth::re::bandwidth::Bandwidth;
51use serde::{Deserialize, Serialize};
52use serde_with::serde_as;
53use tokio::net::TcpListener;
54use tracing::{debug, error, info};
55
56pub const HOPR_TCP_BUFFER_SIZE: usize = 4096;
58
59pub const HOPR_UDP_BUFFER_SIZE: usize = 16384;
61
62pub const HOPR_UDP_QUEUE_SIZE: usize = 8192;
64
65#[cfg(all(feature = "telemetry", not(test)))]
66lazy_static::lazy_static! {
67 static ref METRIC_ACTIVE_CLIENTS: hopr_api::types::telemetry::MultiGauge = hopr_api::types::telemetry::MultiGauge::new(
68 "hopr_session_hoprd_clients",
69 "Number of clients connected at this Entry node",
70 &["type"]
71 ).unwrap();
72}
73
74#[cfg(feature = "explicit-path")]
75pub type Routing = RoutingOptions;
78
79#[cfg(not(feature = "explicit-path"))]
80pub type Routing = HopRouting;
81
82#[serde_as]
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
84pub enum SessionTargetSpec {
86 Plain(String),
87 Sealed(#[serde_as(as = "serde_with::base64::Base64")] Vec<u8>),
88 Service(ServiceId),
89}
90
91impl std::fmt::Display for SessionTargetSpec {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 match self {
94 SessionTargetSpec::Plain(t) => write!(f, "{t}"),
95 SessionTargetSpec::Sealed(t) => write!(f, "$${}", base64::prelude::BASE64_URL_SAFE.encode(t)),
96 SessionTargetSpec::Service(t) => write!(f, "#{t}"),
97 }
98 }
99}
100
101impl FromStr for SessionTargetSpec {
102 type Err = HoprLibError;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 Ok(if let Some(stripped) = s.strip_prefix("$$") {
106 Self::Sealed(
107 base64::prelude::BASE64_URL_SAFE
108 .decode(stripped)
109 .map_err(|e| HoprLibError::Other(e.into()))?,
110 )
111 } else if let Some(stripped) = s.strip_prefix("#") {
112 Self::Service(
113 stripped
114 .parse()
115 .map_err(|_| HoprLibError::GeneralError("cannot parse service id".into()))?,
116 )
117 } else {
118 Self::Plain(s.to_owned())
119 })
120 }
121}
122
123impl SessionTargetSpec {
124 pub fn into_target(self, protocol: IpProtocol) -> Result<SessionTarget, HoprLibError> {
125 Ok(match (protocol, self) {
126 (IpProtocol::TCP, SessionTargetSpec::Plain(plain)) => {
127 SessionTarget::TcpStream(IpOrHost::from_str(&plain).map(SealedHost::from)?)
128 }
129 (IpProtocol::UDP, SessionTargetSpec::Plain(plain)) => {
130 SessionTarget::UdpStream(IpOrHost::from_str(&plain).map(SealedHost::from)?)
131 }
132 (IpProtocol::TCP, SessionTargetSpec::Sealed(enc)) => {
133 SessionTarget::TcpStream(SealedHost::Sealed(enc.into_boxed_slice()))
134 }
135 (IpProtocol::UDP, SessionTargetSpec::Sealed(enc)) => {
136 SessionTarget::UdpStream(SealedHost::Sealed(enc.into_boxed_slice()))
137 }
138 (_, SessionTargetSpec::Service(id)) => SessionTarget::ExitNode(id),
139 })
140 }
141}
142
143#[derive(Debug)]
145pub struct ClientEntry {
146 pub sock_addr: SocketAddr,
148 pub abort_handle: AbortHandle,
150 pub configurator: HoprSessionConfigurator,
152}
153
154#[derive(Debug)]
156pub struct StoredSessionEntry {
157 pub destination: Address,
159 pub target: SessionTargetSpec,
161 pub forward_path: Routing,
163 pub return_path: Routing,
165 pub max_client_sessions: usize,
167 pub max_surb_upstream: Option<human_bandwidth::re::bandwidth::Bandwidth>,
169 pub response_buffer: Option<bytesize::ByteSize>,
172 pub session_pool: Option<usize>,
174 pub abort_handle: AbortHandle,
176
177 clients: Arc<DashMap<SessionId, ClientEntry>>,
178}
179
180impl StoredSessionEntry {
181 pub fn get_clients(&self) -> &Arc<DashMap<SessionId, ClientEntry>> {
182 &self.clients
183 }
184}
185
186pub fn build_binding_host(requested: Option<&str>, default: std::net::SocketAddr) -> std::net::SocketAddr {
191 match requested.map(|r| std::net::SocketAddr::from_str(r).map_err(|_| r)) {
192 Some(Err(requested)) => {
193 debug!(requested, %default, "using partially default listen host");
195 std::net::SocketAddr::new(
196 requested.parse().unwrap_or(default.ip()),
197 requested
198 .strip_prefix(":")
199 .and_then(|p| u16::from_str(p).ok())
200 .unwrap_or(default.port()),
201 )
202 }
203 Some(Ok(requested)) => {
204 debug!(%requested, "using requested listen host");
205 requested
206 }
207 None => {
208 debug!(%default, "using default listen host");
209 default
210 }
211 }
212}
213
214#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
215pub struct ListenerId(pub IpProtocol, pub std::net::SocketAddr);
216
217impl std::fmt::Display for ListenerId {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 write!(f, "{}://{}:{}", self.0, self.1.ip(), self.1.port())
220 }
221}
222
223#[derive(Default)]
224pub struct ListenerJoinHandles(pub DashMap<ListenerId, StoredSessionEntry>);
225
226impl ListenerJoinHandles {
227 pub fn find_configurator(&self, session_id: &SessionId) -> Option<HoprSessionConfigurator> {
229 self.0.iter().find_map(|entry| {
230 entry
231 .value()
232 .get_clients()
233 .get(session_id)
234 .map(|client| client.value().configurator.clone())
235 })
236 }
237
238 pub fn configurators_for(&self, bound_host: std::net::SocketAddr) -> Vec<HoprSessionConfigurator> {
241 self.0
242 .get(&ListenerId(IpProtocol::TCP, bound_host))
243 .map(|entry| {
244 entry
245 .get_clients()
246 .iter()
247 .map(|client| client.value().configurator.clone())
248 .collect()
249 })
250 .unwrap_or_default()
251 }
252}
253
254impl Abortable for ListenerJoinHandles {
255 fn abort_task(&self) {
256 self.0.alter_all(|_, v| {
257 v.abort_handle.abort();
258 v
259 });
260 }
261
262 fn was_aborted(&self) -> bool {
263 self.0.iter().all(|v| v.abort_handle.is_aborted())
264 }
265}
266
267#[async_trait::async_trait]
272pub trait SessionFactory: Clone + Send + Sync + 'static {
273 type Cfg: Clone + Send + 'static;
274
275 async fn create_session(
277 &self,
278 dest: Address,
279 target: SessionTarget,
280 cfg: Self::Cfg,
281 ) -> Result<(HoprSession, HoprSessionConfigurator), anyhow::Error>;
282
283 fn routing_from_cfg(&self, cfg: &Self::Cfg) -> Result<(Routing, Routing), anyhow::Error>;
285
286 fn listener_limits(&self, cfg: &Self::Cfg)
288 -> (Option<human_bandwidth::re::bandwidth::Bandwidth>, Option<ByteSize>);
289
290 fn session_idle_timeout(&self) -> Option<std::time::Duration>;
292}
293
294pub struct HopSessionFactory<Chain, Graph, Net, TMgr> {
295 hopr: Arc<Hopr<Chain, Graph, Net, TMgr>>,
296}
297
298impl<Chain, Graph, Net, TMgr> HopSessionFactory<Chain, Graph, Net, TMgr> {
299 pub fn new(hopr: Arc<Hopr<Chain, Graph, Net, TMgr>>) -> Self {
300 Self { hopr }
301 }
302}
303
304impl<Chain, Graph, Net, TMgr> Clone for HopSessionFactory<Chain, Graph, Net, TMgr> {
305 fn clone(&self) -> Self {
306 Self {
307 hopr: self.hopr.clone(),
308 }
309 }
310}
311
312#[async_trait::async_trait]
313impl<Chain, Graph, Net, TMgr> SessionFactory for HopSessionFactory<Chain, Graph, Net, TMgr>
314where
315 Chain: HoprChainApi + Clone + Send + Sync + 'static,
316 Graph: NetworkGraphView<NodeId = OffchainPublicKey>
317 + NetworkGraphUpdate
318 + NetworkGraphWrite<NodeId = OffchainPublicKey>
319 + NetworkGraphTraverse<NodeId = OffchainPublicKey>
320 + Clone
321 + Send
322 + Sync
323 + 'static,
324 <Graph as NetworkGraphTraverse>::Observed: EdgeObservableRead + Send + 'static,
325 <Graph as NetworkGraphWrite>::Observed: EdgeObservableWrite + Send,
326 Net: NetworkView + NetworkStreamControl + Send + Sync + Clone + 'static,
327 TMgr: Send + Sync + 'static,
328{
329 type Cfg = HoprSessionClientConfig;
330
331 async fn create_session(
332 &self,
333 dest: Address,
334 target: SessionTarget,
335 cfg: Self::Cfg,
336 ) -> Result<(HoprSession, HoprSessionConfigurator), anyhow::Error> {
337 Ok(HoprSessionClientOperations::connect_to(self.hopr.as_ref(), dest, target, cfg).await?)
338 }
339
340 fn routing_from_cfg(&self, cfg: &Self::Cfg) -> Result<(Routing, Routing), anyhow::Error> {
341 #[allow(clippy::useless_conversion)]
344 Ok((cfg.forward_path.into(), cfg.return_path.into()))
345 }
346
347 fn listener_limits(
348 &self,
349 cfg: &Self::Cfg,
350 ) -> (Option<human_bandwidth::re::bandwidth::Bandwidth>, Option<ByteSize>) {
351 (
352 cfg.surb_management
353 .map(|v| Bandwidth::from_bps(v.max_surbs_per_sec * SURB_SIZE as u64)),
354 cfg.surb_management
355 .map(|v| ByteSize::b(v.target_surb_buffer_size * SURB_SIZE as u64)),
356 )
357 }
358
359 fn session_idle_timeout(&self) -> Option<std::time::Duration> {
360 Some(self.hopr.config().protocol.session.idle_timeout)
361 }
362}
363
364#[cfg(feature = "explicit-path")]
365pub struct ExplicitPathSessionFactory<Chain, Graph, Net, TMgr> {
366 hopr: Arc<Hopr<Chain, Graph, Net, TMgr>>,
367}
368
369#[cfg(feature = "explicit-path")]
370impl<Chain, Graph, Net, TMgr> ExplicitPathSessionFactory<Chain, Graph, Net, TMgr> {
371 pub fn new(hopr: Arc<Hopr<Chain, Graph, Net, TMgr>>) -> Self {
372 Self { hopr }
373 }
374}
375
376#[cfg(feature = "explicit-path")]
377impl<Chain, Graph, Net, TMgr> Clone for ExplicitPathSessionFactory<Chain, Graph, Net, TMgr> {
378 fn clone(&self) -> Self {
379 Self {
380 hopr: self.hopr.clone(),
381 }
382 }
383}
384
385#[cfg(feature = "explicit-path")]
386#[async_trait::async_trait]
387impl<Chain, Graph, Net, TMgr> SessionFactory for ExplicitPathSessionFactory<Chain, Graph, Net, TMgr>
388where
389 Chain: HoprChainApi + Clone + Send + Sync + 'static,
390 Graph: NetworkGraphView<NodeId = OffchainPublicKey>
391 + NetworkGraphUpdate
392 + NetworkGraphWrite<NodeId = OffchainPublicKey>
393 + NetworkGraphTraverse<NodeId = OffchainPublicKey>
394 + Clone
395 + Send
396 + Sync
397 + 'static,
398 <Graph as NetworkGraphTraverse>::Observed: EdgeObservableRead + Send + 'static,
399 <Graph as NetworkGraphWrite>::Observed: EdgeObservableWrite + Send,
400 Net: NetworkView + NetworkStreamControl + Send + Sync + Clone + 'static,
401 TMgr: Send + Sync + 'static,
402{
403 type Cfg = HoprSessionClientExplicitPathConfig;
404
405 async fn create_session(
406 &self,
407 dest: Address,
408 target: SessionTarget,
409 cfg: Self::Cfg,
410 ) -> Result<(HoprSession, HoprSessionConfigurator), anyhow::Error> {
411 Ok(self.hopr.connect_to_using_explicit_path(dest, target, cfg).await?)
412 }
413
414 fn routing_from_cfg(&self, cfg: &Self::Cfg) -> Result<(Routing, Routing), anyhow::Error> {
415 let forward_path = RoutingOptions::IntermediatePath(
416 cfg.forward_path
417 .clone()
418 .try_into()
419 .map_err(|e| anyhow!("invalid explicit forward path: {e}"))?,
420 );
421 let return_path = RoutingOptions::IntermediatePath(
422 cfg.return_path
423 .clone()
424 .try_into()
425 .map_err(|e| anyhow!("invalid explicit return path: {e}"))?,
426 );
427 Ok((forward_path, return_path))
428 }
429
430 fn listener_limits(
431 &self,
432 cfg: &Self::Cfg,
433 ) -> (Option<human_bandwidth::re::bandwidth::Bandwidth>, Option<ByteSize>) {
434 (
435 cfg.surb_management
436 .map(|v| Bandwidth::from_bps(v.max_surbs_per_sec * SURB_SIZE as u64)),
437 cfg.surb_management
438 .map(|v| ByteSize::b(v.target_surb_buffer_size * SURB_SIZE as u64)),
439 )
440 }
441
442 fn session_idle_timeout(&self) -> Option<std::time::Duration> {
443 Some(self.hopr.config().protocol.session.idle_timeout)
444 }
445}
446
447type SessionPoolInner = Arc<parking_lot::Mutex<VecDeque<(HoprSession, HoprSessionConfigurator)>>>;
448
449pub struct SessionPool {
450 pool: Option<SessionPoolInner>,
451 ah: Option<AbortHandle>,
452}
453
454impl SessionPool {
455 pub const MAX_SESSION_POOL_SIZE: usize = 5;
456
457 pub async fn new<T: SessionFactory>(
458 size: usize,
459 dst: Address,
460 target: SessionTarget,
461 cfg: T::Cfg,
462 factory: T,
463 ) -> Result<Self, anyhow::Error> {
464 let pool = Arc::new(parking_lot::Mutex::new(VecDeque::with_capacity(size)));
465 let factory_clone = factory.clone();
466 let pool_clone = pool.clone();
467 futures::stream::iter(0..size.min(Self::MAX_SESSION_POOL_SIZE))
468 .map(Ok)
469 .try_for_each_concurrent(Self::MAX_SESSION_POOL_SIZE, move |i| {
470 let pool = pool_clone.clone();
471 let factory = factory_clone.clone();
472 let target = target.clone();
473 let cfg = cfg.clone();
474 async move {
475 match factory.create_session(dst, target.clone(), cfg.clone()).await {
476 Ok((session, configurator)) => {
477 debug!(session_id = %session.id(), num_session = i, "created a new session in pool");
478 pool.lock().push_back((session, configurator));
479 Ok(())
480 }
481 Err(error) => {
482 error!(%error, num_session = i, "failed to establish session for pool");
483 Err(anyhow!("failed to establish session #{i} in pool to {dst}: {error}"))
484 }
485 }
486 }
487 })
488 .await?;
489
490 if let Some(timeout) = factory.session_idle_timeout().filter(|_| !pool.lock().is_empty()) {
491 let pool_clone_1 = pool.clone();
492 let pool_clone_2 = pool.clone();
493 Ok(Self {
494 pool: Some(pool),
495 ah: Some(hopr_utils::spawn_as_abortable!(
496 futures_time::stream::interval(futures_time::time::Duration::from(
497 std::time::Duration::from_secs(1).max(timeout / 2)
498 ))
499 .take_while(move |_| futures::future::ready(!pool_clone_1.lock().is_empty()))
500 .for_each(move |_| {
501 let pool = pool_clone_2.clone();
502 async move {
503 let configurators: Vec<_> = pool.lock().iter().map(|(_, cfg)| cfg.clone()).collect();
504 let mut dead_ids = Vec::new();
505 for configurator in &configurators {
506 if let Err(error) = configurator.ping().await {
507 let id = *configurator.id();
508 error!(%error, session_id = %id, "session in pool is not alive, will remove");
509 dead_ids.push(id);
510 }
511 }
512 if !dead_ids.is_empty() {
513 pool.lock().retain(|(_, cfg)| !dead_ids.contains(cfg.id()));
514 }
515 }
516 })
517 )),
518 })
519 } else {
520 Ok(Self { pool: None, ah: None })
521 }
522 }
523
524 pub fn pop(&mut self) -> Option<(HoprSession, HoprSessionConfigurator)> {
525 self.pool.as_ref().and_then(|pool| pool.lock().pop_front())
526 }
527}
528
529impl Drop for SessionPool {
530 fn drop(&mut self) {
531 if let Some(ah) = self.ah.take() {
532 ah.abort();
533 }
534 }
535}
536
537#[allow(clippy::too_many_arguments)]
538pub async fn create_tcp_client_binding<T: SessionFactory>(
539 bind_host: std::net::SocketAddr,
540 port_range: Option<String>,
541 factory: T,
542 open_listeners: Arc<ListenerJoinHandles>,
543 destination: Address,
544 target_spec: SessionTargetSpec,
545 config: T::Cfg,
546 use_session_pool: Option<usize>,
547 max_client_sessions: Option<usize>,
548) -> Result<(std::net::SocketAddr, Option<SessionId>, usize), BindError> {
549 let (bound_host, tcp_listener) = tcp_listen_on(bind_host, port_range).await.map_err(|e| {
551 if e.kind() == std::io::ErrorKind::AddrInUse {
552 BindError::ListenHostAlreadyUsed
553 } else {
554 BindError::UnknownFailure(format!("failed to start TCP listener on {bind_host}: {e}"))
555 }
556 })?;
557 info!(%bound_host, "TCP session listener bound");
558
559 let target = target_spec
562 .clone()
563 .into_target(IpProtocol::TCP)
564 .map_err(|e| BindError::UnknownFailure(e.to_string()))?;
565 let (forward_path, return_path) = factory
566 .routing_from_cfg(&config)
567 .map_err(|e| BindError::UnknownFailure(e.to_string()))?;
568 let (max_surb_upstream, response_buffer) = factory.listener_limits(&config);
569
570 let session_pool_size = use_session_pool.unwrap_or(0);
572 let mut session_pool = SessionPool::new(
573 session_pool_size,
574 destination,
575 target.clone(),
576 config.clone(),
577 factory.clone(),
578 )
579 .await
580 .map_err(|e| BindError::UnknownFailure(e.to_string()))?;
581
582 let active_sessions = Arc::new(DashMap::new());
583 let mut max_clients = max_client_sessions.unwrap_or(5).max(1);
584
585 if max_clients < session_pool_size {
586 max_clients = session_pool_size;
587 }
588
589 let config_clone = config.clone();
590 let (abort_handle, abort_reg) = AbortHandle::new_pair();
592 let active_sessions_clone = active_sessions.clone();
593 hopr_utils::runtime::prelude::spawn(async move {
594 let active_sessions_clone_2 = active_sessions_clone.clone();
595
596 hopr_utils::runtime::DropAbortable::new_with_registration(
597 tokio_stream::wrappers::TcpListenerStream::new(tcp_listener),
598 abort_reg,
599 )
600 .and_then(|sock| async { Ok((sock.peer_addr()?, sock)) })
601 .for_each(move |accepted_client| {
602 let data = config_clone.clone();
603 let target = target.clone();
604 let factory = factory.clone();
605 let active_sessions = active_sessions_clone_2.clone();
606 let has_capacity = accepted_client.is_ok() && active_sessions.len() < max_clients;
607 let maybe_pooled = has_capacity.then(|| session_pool.pop()).flatten();
608
609 async move {
610 match accepted_client {
611 Ok((sock_addr, mut stream)) => {
612 debug!(?sock_addr, "incoming TCP connection");
613
614 if active_sessions.len() >= max_clients {
617 error!(?bind_host, "no more client slots available at listener");
618 use tokio::io::AsyncWriteExt;
619 if let Err(error) = stream.shutdown().await {
620 error!(%error, ?sock_addr, "failed to shutdown TCP connection");
621 }
622 return;
623 }
624
625 let (session, configurator) = match maybe_pooled {
627 Some((s, c)) => {
628 debug!(session_id = %s.id(), "using pooled session");
629 (s, c)
630 }
631 None => {
632 debug!("no more active sessions in the pool, creating a new one");
633 match factory.create_session(destination, target, data).await {
634 Ok((s, c)) => (s, c),
635 Err(error) => {
636 error!(%error, "failed to establish session");
637 return;
638 }
639 }
640 }
641 };
642
643 let session_id = *session.id();
644 debug!(?sock_addr, %session_id, "new session for incoming TCP connection");
645
646 let (abort_handle, abort_reg) = AbortHandle::new_pair();
647 active_sessions.insert(
648 session_id,
649 ClientEntry {
650 sock_addr,
651 abort_handle,
652 configurator,
653 },
654 );
655
656 #[cfg(all(feature = "telemetry", not(test)))]
657 METRIC_ACTIVE_CLIENTS.increment(&["tcp"], 1.0);
658
659 hopr_utils::runtime::prelude::spawn(
660 bind_session_to_stream(session, stream, HOPR_TCP_BUFFER_SIZE, Some(abort_reg)).then(
663 move |_| async move {
664 active_sessions.remove(&session_id);
667
668 debug!(%session_id, "tcp session has ended");
669
670 #[cfg(all(feature = "telemetry", not(test)))]
671 METRIC_ACTIVE_CLIENTS.decrement(&["tcp"], 1.0);
672 },
673 ),
674 );
675 }
676 Err(error) => error!(%error, "failed to accept connection"),
677 }
678 }
679 })
680 .await;
681
682 active_sessions_clone.iter().for_each(|entry| {
684 let client = entry.value();
685 debug!(session_id = %entry.key(), sock_addr = ?client.sock_addr, "aborting opened TCP session after listener has been closed");
686 client.abort_handle.abort()
687 });
688 });
689
690 open_listeners.0.insert(
691 ListenerId(hopr_utils::network_types::types::IpProtocol::TCP, bound_host),
692 StoredSessionEntry {
693 destination,
694 target: target_spec,
695 forward_path,
696 return_path,
697 clients: active_sessions,
698 max_client_sessions: max_clients,
699 max_surb_upstream,
700 response_buffer,
701 session_pool: Some(session_pool_size),
702 abort_handle,
703 },
704 );
705 Ok((bound_host, None, max_clients))
706}
707
708#[derive(Debug, thiserror::Error)]
709pub enum BindError {
710 #[error("conflict detected: listen host already in use")]
711 ListenHostAlreadyUsed,
712
713 #[error("unknown failure: {0}")]
714 UnknownFailure(String),
715}
716
717pub async fn create_udp_client_binding<T: SessionFactory>(
718 bind_host: std::net::SocketAddr,
719 port_range: Option<String>,
720 factory: T,
721 open_listeners: Arc<ListenerJoinHandles>,
722 destination: Address,
723 target_spec: SessionTargetSpec,
724 config: T::Cfg,
725) -> Result<(std::net::SocketAddr, Option<SessionId>, usize), BindError> {
726 let (bound_host, udp_socket) = udp_bind_to(bind_host, port_range).await.map_err(|e| {
728 if e.kind() == std::io::ErrorKind::AddrInUse {
729 BindError::ListenHostAlreadyUsed
730 } else {
731 BindError::UnknownFailure(format!("failed to start UDP listener on {bind_host}: {e}"))
732 }
733 })?;
734
735 info!(%bound_host, "UDP session listener bound");
736
737 let target = target_spec
738 .clone()
739 .into_target(IpProtocol::UDP)
740 .map_err(|e| BindError::UnknownFailure(e.to_string()))?;
741 let (forward_path, return_path) = factory
742 .routing_from_cfg(&config)
743 .map_err(|e| BindError::UnknownFailure(e.to_string()))?;
744 let (max_surb_upstream, response_buffer) = factory.listener_limits(&config);
745
746 let (session, configurator) = factory
748 .create_session(destination, target, config.clone())
749 .await
750 .map_err(|e| BindError::UnknownFailure(e.to_string()))?;
751
752 let open_listeners_clone = open_listeners.clone();
753 let listener_id = ListenerId(hopr_utils::network_types::types::IpProtocol::UDP, bound_host);
754
755 let (abort_handle, abort_reg) = AbortHandle::new_pair();
766 let clients = Arc::new(DashMap::new());
767 let max_clients: usize = 1; let session_id = *session.id();
771 clients.insert(
772 session_id,
773 ClientEntry {
774 sock_addr: bound_host,
775 abort_handle: abort_handle.clone(),
776 configurator,
777 },
778 );
779 hopr_utils::runtime::prelude::spawn(async move {
780 #[cfg(all(feature = "telemetry", not(test)))]
781 METRIC_ACTIVE_CLIENTS.increment(&["udp"], 1.0);
782
783 bind_session_to_stream(session, udp_socket, HOPR_UDP_BUFFER_SIZE, Some(abort_reg)).await;
784
785 #[cfg(all(feature = "telemetry", not(test)))]
786 METRIC_ACTIVE_CLIENTS.decrement(&["udp"], 1.0);
787
788 open_listeners_clone.0.remove(&listener_id);
790 });
791
792 open_listeners.0.insert(
793 listener_id,
794 StoredSessionEntry {
795 destination,
796 target: target_spec,
797 forward_path,
798 return_path,
799 max_client_sessions: max_clients,
800 max_surb_upstream,
801 response_buffer,
802 session_pool: None,
803 abort_handle,
804 clients,
805 },
806 );
807 Ok((bound_host, Some(session_id), max_clients))
808}
809
810async fn try_restricted_bind<F, S, Fut>(
811 addrs: Vec<std::net::SocketAddr>,
812 range_str: &str,
813 binder: F,
814) -> std::io::Result<S>
815where
816 F: Fn(Vec<std::net::SocketAddr>) -> Fut,
817 Fut: Future<Output = std::io::Result<S>>,
818{
819 if addrs.is_empty() {
820 return Err(std::io::Error::other("no valid socket addresses found"));
821 }
822
823 let range = range_str
824 .split_once(":")
825 .and_then(
826 |(a, b)| match u16::from_str(a).and_then(|a| Ok((a, u16::from_str(b)?))) {
827 Ok((a, b)) if a <= b => Some(a..=b),
828 _ => None,
829 },
830 )
831 .ok_or(std::io::Error::other(format!("invalid port range {range_str}")))?;
832
833 for port in range {
834 let addrs = addrs
835 .iter()
836 .map(|addr| std::net::SocketAddr::new(addr.ip(), port))
837 .collect::<Vec<_>>();
838 match binder(addrs).await {
839 Ok(listener) => return Ok(listener),
840 Err(error) => debug!(%error, "listen address not usable"),
841 }
842 }
843
844 Err(std::io::Error::new(
845 std::io::ErrorKind::AddrNotAvailable,
846 format!("no valid socket addresses found within range: {range_str}"),
847 ))
848}
849
850async fn tcp_listen_on<A: std::net::ToSocketAddrs>(
852 address: A,
853 port_range: Option<String>,
854) -> std::io::Result<(std::net::SocketAddr, TcpListener)> {
855 let addrs = address.to_socket_addrs()?.collect::<Vec<_>>();
856
857 if addrs.iter().all(|a| a.port() == 0)
860 && let Some(range_str) = port_range
861 {
862 let tcp_listener = try_restricted_bind(
863 addrs,
864 &range_str,
865 |a| async move { TcpListener::bind(a.as_slice()).await },
866 )
867 .await?;
868 return Ok((tcp_listener.local_addr()?, tcp_listener));
869 }
870
871 let tcp_listener = TcpListener::bind(addrs.as_slice()).await?;
872 Ok((tcp_listener.local_addr()?, tcp_listener))
873}
874
875pub async fn udp_bind_to<A: std::net::ToSocketAddrs>(
876 address: A,
877 port_range: Option<String>,
878) -> std::io::Result<(std::net::SocketAddr, ConnectedUdpStream)> {
879 let addrs = address.to_socket_addrs()?.collect::<Vec<_>>();
880
881 let builder = ConnectedUdpStream::builder()
882 .with_buffer_size(HOPR_UDP_BUFFER_SIZE)
883 .with_foreign_data_mode(ForeignDataMode::Discard) .with_queue_size(HOPR_UDP_QUEUE_SIZE)
885 .with_receiver_parallelism(
886 std::env::var("HOPRD_SESSION_ENTRY_UDP_RX_PARALLELISM")
887 .ok()
888 .and_then(|s| s.parse::<NonZeroUsize>().ok())
889 .map(UdpStreamParallelism::Specific)
890 .unwrap_or(UdpStreamParallelism::Auto),
891 );
892
893 if addrs.iter().all(|a| a.port() == 0)
896 && let Some(range_str) = port_range
897 {
898 let udp_listener = try_restricted_bind(addrs, &range_str, |addrs| {
899 futures::future::ready(builder.clone().build(addrs.as_slice()))
900 })
901 .await?;
902
903 return Ok((*udp_listener.bound_address(), udp_listener));
904 }
905
906 let udp_socket = builder.build(address)?;
907 Ok((*udp_socket.bound_address(), udp_socket))
908}
909
910async fn bind_session_to_stream<T>(
911 mut session: HoprSession,
912 mut stream: T,
913 max_buf: usize,
914 abort_reg: Option<AbortRegistration>,
915) where
916 T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
917{
918 let session_id = *session.id();
919 match transfer_session(&mut session, &mut stream, max_buf, abort_reg).await {
920 Ok((session_to_stream_bytes, stream_to_session_bytes)) => info!(
921 session_id = ?session_id,
922 session_to_stream_bytes, stream_to_session_bytes, "client session ended",
923 ),
924 Err(error) => error!(
925 session_id = ?session_id,
926 %error,
927 "error during data transfer"
928 ),
929 }
930}
931
932#[cfg(test)]
933mod tests {
934 use anyhow::Context;
935 use futures::{
936 FutureExt, StreamExt,
937 channel::mpsc::{UnboundedReceiver, UnboundedSender},
938 };
939 use futures_time::future::FutureExt as TimeFutureExt;
940 use hopr_api::types::crypto::crypto_traits::Randomizable;
941 use hopr_lib::{
942 api::types::{
943 internal::{
944 prelude::HoprPseudonym,
945 routing::{DestinationRouting, RoutingOptions},
946 },
947 primitive::prelude::Address,
948 },
949 exports::transport::{ApplicationData, ApplicationDataIn, ApplicationDataOut, HoprSession},
950 };
951 use hopr_transport::session::HoprSessionConfig;
952 use tokio::io::{AsyncReadExt, AsyncWriteExt};
953
954 use super::*;
955
956 fn loopback_transport() -> (
957 UnboundedSender<(DestinationRouting, ApplicationDataOut)>,
958 UnboundedReceiver<ApplicationDataIn>,
959 ) {
960 let (input_tx, input_rx) = futures::channel::mpsc::unbounded::<(DestinationRouting, ApplicationDataOut)>();
961 let (output_tx, output_rx) = futures::channel::mpsc::unbounded::<ApplicationDataIn>();
962 tokio::task::spawn(
963 input_rx
964 .map(|(_, data)| {
965 Ok(ApplicationDataIn {
966 data: data.data,
967 packet_info: Default::default(),
968 })
969 })
970 .forward(output_tx)
971 .map(|e| tracing::debug!(?e, "loopback transport completed")),
972 );
973
974 (input_tx, output_rx)
975 }
976
977 #[tokio::test]
978 async fn hoprd_session_connection_should_create_a_working_tcp_socket_through_which_data_can_be_sent_and_received()
979 -> anyhow::Result<()> {
980 let session_id = HoprPseudonym::random();
981 let peer: Address = "0x5112D584a1C72Fc250176B57aEba5fFbbB287D8F".parse()?;
982 let cfg = HoprSessionConfig::default();
983 let session = HoprSession::new(
984 session_id,
985 DestinationRouting::forward_only(peer, RoutingOptions::IntermediatePath(Default::default())),
986 cfg,
987 loopback_transport(),
988 None,
989 )?;
990
991 let (bound_addr, tcp_listener) = tcp_listen_on(("127.0.0.1", 0), None)
992 .await
993 .context("listen_on failed")?;
994
995 tokio::task::spawn(async move {
996 match tcp_listener.accept().await {
997 Ok((stream, _)) => bind_session_to_stream(session, stream, HOPR_TCP_BUFFER_SIZE, None).await,
998 Err(e) => error!("failed to accept connection: {e}"),
999 }
1000 });
1001
1002 let mut tcp_stream = tokio::net::TcpStream::connect(bound_addr)
1003 .await
1004 .context("connect failed")?;
1005
1006 let data = vec![b"hello", b"world", b"this ", b"is ", b" a", b" test"];
1007
1008 for d in data.clone().into_iter() {
1009 tcp_stream.write_all(d).await.context("write failed")?;
1010 }
1011
1012 for d in data.iter() {
1013 let mut buf = vec![0; d.len()];
1014 tcp_stream.read_exact(&mut buf).await.context("read failed")?;
1015 }
1016
1017 Ok(())
1018 }
1019
1020 #[test_log::test(tokio::test)]
1021 async fn hoprd_session_connection_should_create_a_working_udp_socket_through_which_data_can_be_sent_and_received()
1022 -> anyhow::Result<()> {
1023 let session_id = HoprPseudonym::random();
1024 let peer: Address = "0x5112D584a1C72Fc250176B57aEba5fFbbB287D8F".parse()?;
1025 let cfg = HoprSessionConfig::default();
1026 let session = HoprSession::new(
1027 session_id,
1028 DestinationRouting::forward_only(peer, RoutingOptions::IntermediatePath(Default::default())),
1029 cfg,
1030 loopback_transport(),
1031 None,
1032 )?;
1033
1034 let (listen_addr, udp_listener) = udp_bind_to(("127.0.0.1", 0), None)
1035 .await
1036 .context("udp_bind_to failed")?;
1037
1038 let (abort_handle, abort_registration) = AbortHandle::new_pair();
1039 let jh = tokio::task::spawn(bind_session_to_stream(
1040 session,
1041 udp_listener,
1042 ApplicationData::PAYLOAD_SIZE,
1043 Some(abort_registration),
1044 ));
1045
1046 let mut udp_stream = ConnectedUdpStream::builder()
1047 .with_buffer_size(ApplicationData::PAYLOAD_SIZE)
1048 .with_queue_size(HOPR_UDP_QUEUE_SIZE)
1049 .with_counterparty(listen_addr)
1050 .build(("127.0.0.1", 0))
1051 .context("bind failed")?;
1052
1053 let data = vec![b"hello", b"world", b"this ", b"is ", b" a", b" test"];
1054
1055 for d in data.clone().into_iter() {
1056 udp_stream.write_all(d).await.context("write failed")?;
1057 }
1059
1060 for d in data.iter() {
1061 let mut buf = vec![0; d.len()];
1062 udp_stream.read_exact(&mut buf).await.context("read failed")?;
1063 }
1064
1065 abort_handle.abort();
1067 jh.timeout(futures_time::time::Duration::from_millis(200)).await??;
1068
1069 Ok(())
1070 }
1071
1072 fn stub_stored_entry() -> StoredSessionEntry {
1073 let (abort_handle, _) = AbortHandle::new_pair();
1074 StoredSessionEntry {
1075 destination: Address::default(),
1076 target: SessionTargetSpec::Plain("localhost:8080".into()),
1077 forward_path: Default::default(),
1078 return_path: Default::default(),
1079 max_client_sessions: 5,
1080 max_surb_upstream: None,
1081 response_buffer: None,
1082 session_pool: None,
1083 abort_handle,
1084 clients: Arc::new(DashMap::new()),
1085 }
1086 }
1087
1088 #[test]
1089 fn find_configurator_should_return_none_when_no_listeners() {
1090 let handles = ListenerJoinHandles::default();
1091 let session_id = HoprPseudonym::random();
1092 assert!(handles.find_configurator(&session_id).is_none());
1093 }
1094
1095 #[test]
1096 fn find_configurator_should_return_none_when_session_not_tracked() {
1097 let handles = ListenerJoinHandles::default();
1098 let listener_id = ListenerId(IpProtocol::TCP, "127.0.0.1:9091".parse().unwrap());
1099 handles.0.insert(listener_id, stub_stored_entry());
1100
1101 let session_id = HoprPseudonym::random();
1102 assert!(handles.find_configurator(&session_id).is_none());
1103 }
1104
1105 #[test]
1106 fn configurators_for_returns_empty_for_unknown_host() {
1107 let handles = ListenerJoinHandles::default();
1108 let addr: std::net::SocketAddr = "127.0.0.1:9999".parse().unwrap();
1109 assert!(handles.configurators_for(addr).is_empty());
1110 }
1111
1112 #[test]
1113 fn configurators_for_returns_empty_vec_when_listener_has_no_clients() {
1114 let handles = ListenerJoinHandles::default();
1115 let addr: std::net::SocketAddr = "127.0.0.1:9091".parse().unwrap();
1116 handles.0.insert(ListenerId(IpProtocol::TCP, addr), stub_stored_entry());
1117 assert!(handles.configurators_for(addr).is_empty());
1119 }
1120
1121 #[test]
1122 fn configurators_for_ignores_udp_listener_on_same_port() {
1123 let handles = ListenerJoinHandles::default();
1124 let addr: std::net::SocketAddr = "127.0.0.1:9092".parse().unwrap();
1125 handles.0.insert(ListenerId(IpProtocol::UDP, addr), stub_stored_entry());
1126 assert!(handles.configurators_for(addr).is_empty());
1128 }
1129
1130 #[test]
1131 fn stored_session_entry_clients_should_start_empty() {
1132 let entry = stub_stored_entry();
1133 assert!(entry.get_clients().is_empty());
1134 assert_eq!(entry.max_client_sessions, 5);
1135 }
1136
1137 #[test]
1138 fn session_target_spec_plain_roundtrip() {
1139 let spec = SessionTargetSpec::Plain("localhost:8080".into());
1140 let s = spec.to_string();
1141 assert_eq!(s, "localhost:8080");
1142 assert_eq!(
1143 SessionTargetSpec::from_str(&s).unwrap(),
1144 SessionTargetSpec::Plain("localhost:8080".into())
1145 );
1146 }
1147
1148 #[test]
1149 fn session_target_spec_sealed_roundtrip() {
1150 let data = vec![0xde, 0xad, 0xbe, 0xef];
1151 let spec = SessionTargetSpec::Sealed(data.clone());
1152 let s = spec.to_string();
1153 assert!(s.starts_with("$$"));
1154 assert_eq!(
1155 SessionTargetSpec::from_str(&s).unwrap(),
1156 SessionTargetSpec::Sealed(data)
1157 );
1158 }
1159
1160 #[test]
1161 fn session_target_spec_service_roundtrip() {
1162 let spec = SessionTargetSpec::Service(42);
1163 let s = spec.to_string();
1164 assert_eq!(s, "#42");
1165 assert_eq!(SessionTargetSpec::from_str(&s).unwrap(), SessionTargetSpec::Service(42));
1166 }
1167
1168 #[test]
1169 fn build_binding_address() {
1170 let default = "10.0.0.1:10000".parse().unwrap();
1171
1172 let result = build_binding_host(Some("127.0.0.1:10000"), default);
1173 assert_eq!(result, "127.0.0.1:10000".parse::<std::net::SocketAddr>().unwrap());
1174
1175 let result = build_binding_host(None, default);
1176 assert_eq!(result, "10.0.0.1:10000".parse::<std::net::SocketAddr>().unwrap());
1177
1178 let result = build_binding_host(Some("127.0.0.1"), default);
1179 assert_eq!(result, "127.0.0.1:10000".parse::<std::net::SocketAddr>().unwrap());
1180
1181 let result = build_binding_host(Some(":1234"), default);
1182 assert_eq!(result, "10.0.0.1:1234".parse::<std::net::SocketAddr>().unwrap());
1183
1184 let result = build_binding_host(Some(":"), default);
1185 assert_eq!(result, "10.0.0.1:10000".parse::<std::net::SocketAddr>().unwrap());
1186
1187 let result = build_binding_host(Some(""), default);
1188 assert_eq!(result, "10.0.0.1:10000".parse::<std::net::SocketAddr>().unwrap());
1189 }
1190}