hopr_session_server_forwarder/
lib.rs1pub mod config;
4
5use std::net::SocketAddr;
6
7use hopr_api::types::crypto::prelude::OffchainKeypair;
8use hopr_transport_session::{IncomingSession, ServiceId, SessionTarget, transfer_session};
9use hopr_utils::{
10 network_types::{
11 prelude::ForeignDataMode,
12 udp::{ConnectedUdpStream, UdpStreamParallelism},
13 },
14 parallelize::cpu::spawn_blocking,
15};
16
17use crate::config::SessionIpForwardingConfig;
18
19#[cfg(all(feature = "telemetry", not(test)))]
20lazy_static::lazy_static! {
21 static ref METRIC_ACTIVE_TARGETS: hopr_api::types::telemetry::MultiGauge = hopr_api::types::telemetry::MultiGauge::new(
22 "hopr_session_hoprd_target_connections",
23 "Number of currently active HOPR session target connections on this Exit node",
24 &["type"]
25 ).unwrap();
26}
27
28pub const HOPR_TCP_BUFFER_SIZE: usize = 4096;
30
31pub const HOPR_UDP_BUFFER_SIZE: usize = 16384;
33
34pub const HOPR_UDP_QUEUE_SIZE: usize = 8192;
36
37#[derive(Debug, thiserror::Error)]
39pub enum ForwarderError {
40 #[error("{0}")]
41 General(String),
42}
43
44impl ForwarderError {
45 fn general(s: impl std::fmt::Display) -> Self {
46 Self::General(s.to_string())
47 }
48}
49
50#[derive(Debug, Clone)]
53pub struct HoprServerIpForwardingReactor {
54 keypair: OffchainKeypair,
55 cfg: SessionIpForwardingConfig,
56}
57
58impl HoprServerIpForwardingReactor {
59 pub fn new(keypair: OffchainKeypair, cfg: SessionIpForwardingConfig) -> Self {
60 Self { keypair, cfg }
61 }
62
63 fn all_ips_allowed(&self, addrs: &[SocketAddr]) -> bool {
64 if self.cfg.use_target_allow_list {
65 for addr in addrs {
66 if !self.cfg.target_allow_list.contains(addr) {
67 tracing::error!(%addr, "address not allowed by the target allow list, denying the target");
68 return false;
69 }
70 tracing::debug!(%addr, "address allowed by the target allow list, accepting the target");
71 }
72 }
73 true
74 }
75}
76
77pub const SERVICE_ID_LOOPBACK: ServiceId = 0;
78
79#[async_trait::async_trait]
80impl hopr_api::node::HoprSessionServer for HoprServerIpForwardingReactor {
81 type Error = ForwarderError;
82 type Session = IncomingSession;
83
84 #[tracing::instrument(level = "debug", skip(self, session))]
85 async fn process(&self, mut session: IncomingSession) -> Result<(), ForwarderError> {
86 let session_id = *session.session.id();
87 match session.target {
88 SessionTarget::UdpStream(udp_target) => {
89 let kp = self.keypair.clone();
90 let udp_target = spawn_blocking(move || udp_target.unseal(&kp), "udp_unseal")
91 .await
92 .map_err(|e| ForwarderError::general(format!("failed to spawn unseal task: {e}")))?
93 .map_err(|e| ForwarderError::general(format!("cannot unseal target: {e}")))?;
94
95 tracing::debug!(
96 session_id = ?session_id,
97 %udp_target,
98 "binding socket to the UDP server"
99 );
100
101 let resolved_udp_target = udp_target
104 .clone()
105 .resolve_tokio()
106 .await
107 .map_err(|e| ForwarderError::general(format!("failed to resolve DNS name {udp_target}: {e}")))?
108 .first()
109 .ok_or_else(|| ForwarderError::general(format!("failed to resolve DNS name {udp_target}")))?
110 .to_owned();
111 tracing::debug!(
112 ?session_id,
113 %udp_target,
114 resolution = ?resolved_udp_target,
115 "UDP target resolved"
116 );
117
118 if !self.all_ips_allowed(&[resolved_udp_target]) {
119 return Err(ForwarderError::general(format!(
120 "denied target address {resolved_udp_target}"
121 )));
122 }
123
124 let mut udp_bridge = ConnectedUdpStream::builder()
125 .with_buffer_size(HOPR_UDP_BUFFER_SIZE)
126 .with_counterparty(resolved_udp_target)
127 .with_foreign_data_mode(ForeignDataMode::Error)
128 .with_queue_size(HOPR_UDP_QUEUE_SIZE)
129 .with_receiver_parallelism(
130 self.cfg
131 .udp_rx_parallelism
132 .map(UdpStreamParallelism::Specific)
133 .unwrap_or(UdpStreamParallelism::Auto),
134 )
135 .build(("0.0.0.0", 0))
136 .map_err(|e| {
137 ForwarderError::general(format!("could not bridge the incoming session to {udp_target}: {e}"))
138 })?;
139
140 tracing::debug!(
141 ?session_id,
142 %udp_target,
143 "bridging the session to the UDP server"
144 );
145
146 tokio::task::spawn(async move {
147 #[cfg(all(feature = "telemetry", not(test)))]
148 let _g = hopr_api::types::telemetry::MultiGaugeGuard::new(&METRIC_ACTIVE_TARGETS, &["udp"], 1.0);
149
150 match transfer_session(&mut session.session, &mut udp_bridge, HOPR_UDP_BUFFER_SIZE, None).await {
153 Ok((session_to_stream_bytes, stream_to_session_bytes)) => tracing::info!(
154 ?session_id,
155 session_to_stream_bytes,
156 stream_to_session_bytes,
157 %udp_target,
158 "server bridged session to UDP ended"
159 ),
160 Err(e) => tracing::error!(
161 ?session_id,
162 %udp_target,
163 error = %e,
164 "UDP server stream is closed"
165 ),
166 }
167 });
168
169 Ok(())
170 }
171 SessionTarget::TcpStream(tcp_target) => {
172 let kp = self.keypair.clone();
173 let tcp_target = spawn_blocking(move || tcp_target.unseal(&kp), "tcp_unseal")
174 .await
175 .map_err(|e| ForwarderError::general(format!("failed to spawn unseal task: {e}")))?
176 .map_err(|e| ForwarderError::general(format!("cannot unseal target: {e}")))?;
177
178 tracing::debug!(?session_id, %tcp_target, "creating a connection to the TCP server");
179
180 let resolved_tcp_targets =
183 tcp_target.clone().resolve_tokio().await.map_err(|e| {
184 ForwarderError::general(format!("failed to resolve DNS name {tcp_target}: {e}"))
185 })?;
186 tracing::debug!(
187 ?session_id,
188 %tcp_target,
189 resolution = ?resolved_tcp_targets,
190 "TCP target resolved"
191 );
192
193 if !self.all_ips_allowed(&resolved_tcp_targets) {
194 return Err(ForwarderError::general(format!(
195 "denied target address {resolved_tcp_targets:?}"
196 )));
197 }
198
199 let strategy = tokio_retry::strategy::FixedInterval::new(self.cfg.tcp_target_retry_delay)
200 .take(self.cfg.max_tcp_target_retries as usize);
201
202 let mut tcp_bridge = tokio_retry::Retry::start(strategy, || {
203 tokio::net::TcpStream::connect(resolved_tcp_targets.as_slice())
204 })
205 .await
206 .map_err(|e| {
207 ForwarderError::general(format!("could not bridge the incoming session to {tcp_target}: {e}"))
208 })?;
209
210 tcp_bridge.set_nodelay(true).map_err(|e| {
211 ForwarderError::general(format!(
212 "could not set the TCP_NODELAY option for the bridged session to {tcp_target}: {e}",
213 ))
214 })?;
215
216 tracing::debug!(
217 ?session_id,
218 %tcp_target,
219 "bridging the session to the TCP server"
220 );
221
222 tokio::task::spawn(async move {
223 #[cfg(all(feature = "telemetry", not(test)))]
224 let _g = hopr_api::types::telemetry::MultiGaugeGuard::new(&METRIC_ACTIVE_TARGETS, &["tcp"], 1.0);
225
226 match transfer_session(&mut session.session, &mut tcp_bridge, HOPR_TCP_BUFFER_SIZE, None).await {
227 Ok((session_to_stream_bytes, stream_to_session_bytes)) => tracing::info!(
228 ?session_id,
229 session_to_stream_bytes,
230 stream_to_session_bytes,
231 %tcp_target,
232 "server bridged session to TCP ended"
233 ),
234 Err(error) => tracing::error!(
235 ?session_id,
236 %tcp_target,
237 %error,
238 "TCP server stream is closed"
239 ),
240 }
241 });
242
243 Ok(())
244 }
245 SessionTarget::ExitNode(SERVICE_ID_LOOPBACK) => {
246 tracing::debug!(?session_id, "bridging the session to the loopback service");
247 let (mut reader, mut writer) = tokio::io::split(session.session);
248
249 #[cfg(all(feature = "telemetry", not(test)))]
250 let _g = hopr_api::types::telemetry::MultiGaugeGuard::new(&METRIC_ACTIVE_TARGETS, &["loopback"], 1.0);
251
252 match tokio::io::copy(&mut reader, &mut writer).await {
254 Ok(copied) => tracing::info!(?session_id, copied, "server loopback session service ended"),
255 Err(error) => tracing::error!(
256 ?session_id,
257 %error,
258 "server loopback session service ended with an error"
259 ),
260 }
261
262 Ok(())
263 }
264 SessionTarget::ExitNode(_) => Err(ForwarderError::General(
265 "server does not support internal session processing".into(),
266 )),
267 }
268 }
269}