1use anyhow::{Result, anyhow};
2use quinn::{ClientConfig, Connection, Endpoint, crypto::rustls::QuicClientConfig};
3use std::{
4 net::{IpAddr, Ipv4Addr, SocketAddr},
5 sync::Arc,
6 time::Duration,
7};
8use tokio::{
9 io::{AsyncReadExt as _, AsyncWriteExt as _},
10 net::TcpStream,
11 time::Instant,
12};
13use tokio_rustls::TlsConnector;
14
15use crate::{
16 get_version,
17 utils::{
18 ConnectorPacket, ConnectorPacketType, ConnectorStream, HelloPacket, HelloPacketType,
19 certs::{FingerprintVerifier, NoCertVerification, SkipQuicServerVerification},
20 compute_token_hmac,
21 http::write_http_resp,
22 read_string_from_stream,
23 ssh::{SshPacketHeader, SshPacketType},
24 },
25};
26
27pub struct Options {
28 pub proxy: SocketAddr,
29 pub local: SocketAddr,
30 pub local_ssl: Option<SocketAddr>,
31 pub token: u128,
32 pub redirect_ssl: bool,
33 pub serve_files: bool,
34 pub files_index: bool,
35 pub quic: bool,
36 pub ssh_cmd: Option<String>,
37
38 pub server_fingerprint: Option<String>,
39
40 pub consts: Consts,
41}
42
43#[derive(Debug, Clone)]
44pub struct Consts {
45 pub max_req_time: u128,
46 pub error_html: &'static str,
47 pub list_html: &'static str,
48}
49
50#[derive(Debug)]
51#[allow(dead_code)]
52struct TunnelSettings {
53 proxy_addr: SocketAddr,
54 ssl_addr: SocketAddr,
55 nonssl_addr: SocketAddr,
56 use_quic: bool,
57 ssh_cmd: Option<String>,
58 token: u128,
59
60 serve_files: bool,
61 files_index: bool,
62
63 consts: Arc<Consts>,
64}
65
66#[derive(Clone)]
67struct ConnectionOpener {
68 quic_endpoint: Endpoint,
69 quic_connection: Option<Connection>,
70 tls_connector: Arc<TlsConnector>,
71}
72
73pub async fn spawn_connector(options: Options) {
74 loop {
75 if let Err(e) = connector(&options).await {
76 tracing::error!("Connector error: {e}");
77 }
78
79 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
80 }
81}
82
83async fn connector(options: &Options) -> Result<()> {
84 let tls_verifier: Arc<dyn tokio_rustls::rustls::client::danger::ServerCertVerifier> =
85 match options.server_fingerprint.as_deref() {
86 Some(fp) => FingerprintVerifier::new(fp)?,
87 None => {
88 tracing::warn!(
89 "No --server-fingerprint provided; TLS certificate verification is DISABLED. \
90 Set SERVER_FINGERPRINT to the SHA-256 fingerprint printed by the server at \
91 startup to enable certificate pinning."
92 );
93 Arc::new(NoCertVerification)
94 }
95 };
96
97 let quic_verifier: Arc<dyn rustls::client::danger::ServerCertVerifier> =
98 match options.server_fingerprint.as_deref() {
99 Some(fp) => FingerprintVerifier::new(fp)?,
100 None => SkipQuicServerVerification::new(),
101 };
102
103 let mut endpoint = Endpoint::client(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0))?;
104 endpoint.set_default_client_config(ClientConfig::new(Arc::new(QuicClientConfig::try_from(
105 rustls::ClientConfig::builder()
106 .dangerous()
107 .with_custom_certificate_verifier(quic_verifier)
108 .with_no_client_auth(),
109 )?)));
110
111 let config = tokio_rustls::rustls::ClientConfig::builder()
112 .dangerous()
113 .with_custom_certificate_verifier(tls_verifier)
114 .with_no_client_auth();
115 let connector = TlsConnector::from(Arc::new(config));
116
117 let mut opener = ConnectionOpener {
118 quic_endpoint: endpoint,
119 quic_connection: None,
120 tls_connector: Arc::new(connector),
121 };
122
123 if options.quic {
124 opener.quic_connection = Some(
125 opener
126 .quic_endpoint
127 .connect(options.proxy, "proxy.lan")?
128 .await?,
129 );
130 }
131
132 let opener = Arc::new(opener);
133 let (mut stream, nonce) = if options.quic {
134 let connection = opener
135 .quic_connection
136 .as_ref()
137 .ok_or(anyhow!("Quic Connection ref get"))?;
138 let quic_bi = connection
139 .open_bi()
140 .await
141 .map_err(|e| anyhow!("failed to open stream: {}", e))?;
142
143 let stream_id_bytes = u64::from(quic_bi.0.id()).to_le_bytes();
144 let mut nonce = [0u8; 32];
145 connection
146 .export_keying_material(&mut nonce, b"fkm-proxy-auth", &stream_id_bytes)
147 .map_err(|_| anyhow!("Failed to export QUIC session keying material"))?;
148
149 (ConnectorStream::Quic(quic_bi), nonce)
150 } else {
151 let stream = TcpStream::connect(&options.proxy).await?;
152 stream.set_nodelay(true)?;
153 let stream = opener
154 .tls_connector
155 .connect(
156 rustls::pki_types::ServerName::try_from("proxy.lan")?,
157 stream,
158 )
159 .await?;
160
161 let nonce: [u8; 32] =
162 stream
163 .get_ref()
164 .1
165 .export_keying_material([0u8; 32], b"fkm-proxy-auth", None)?;
166
167 (ConnectorStream::TcpTlsClient(Box::new(stream)), nonce)
168 };
169
170 let mut buf = [0; ConnectorPacket::buf_size()];
171
172 let hello_packet = HelloPacket {
173 hp_type: HelloPacketType::Connector,
174 token_hmac: compute_token_hmac(options.token, &nonce),
175 own_ssl: options.local_ssl.is_some(),
176 redirect_ssl: options.redirect_ssl,
177 ssh_enabled: options.ssh_cmd.is_some(),
178 tunnel_id: 0,
179 version: get_version(),
180 };
181
182 stream.write_all(&hello_packet.to_buf()).await?;
183 let res = stream.read_exact(&mut buf).await;
184 if res.is_err() {
185 tracing::error!("Connector read error: {res:?}. Closing connection.");
186 return Ok(());
187 }
188 let packet = ConnectorPacket::from_buf(&buf);
189 match packet.packet_type {
190 ConnectorPacketType::ConnectorConnected => {}
191 ConnectorPacketType::Close => {
192 let reason = read_string_from_stream(&mut stream, 256).await?;
193 tracing::error!("Closing connector! Close reason: {reason}");
194 if packet.exit {
195 tracing::warn!("Exiting app!");
196 std::process::exit(0);
197 }
198
199 return Ok(());
200 }
201 _ => {
202 tracing::error!("Closing connector! Wrong packet response!");
203 return Ok(());
204 }
205 }
206
207 let nonssl_port = stream.read_u16().await?;
208 let ssl_port = stream.read_u16().await?;
209 let domain = read_string_from_stream(&mut stream, 256).await?;
210 tracing::info!(
211 "Access through:\n - http://{domain}:{nonssl_port}\n - https://{domain}:{ssl_port}"
212 );
213
214 let mut last_ping = tokio::time::interval_at(
215 Instant::now() + Duration::from_secs(30),
216 Duration::from_secs(30),
217 );
218
219 let consts = Arc::new(options.consts.clone());
220 loop {
221 tokio::select! {
222 res = stream.read_exact(&mut buf) => {
223 if res.is_err() {
224 tracing::error!("Connector read error: {res:?}. Closing connection.");
225 return Ok(());
226 }
227
228 let packet = ConnectorPacket::from_buf(&buf);
229 if packet.packet_type == ConnectorPacketType::Ping {
230 stream.write_u8(0x69).await?;
231 last_ping.reset();
232
233 continue; } else if packet.packet_type == ConnectorPacketType::Close {
235 let reason = read_string_from_stream(&mut stream, 256).await?;
236 tracing::error!("Closing connector! Close reason: {reason}");
237 return Ok(());
238 }
239
240 let opener = opener.clone();
241 let requested_time = Instant::now();
242 let settings = TunnelSettings {
243 proxy_addr: options.proxy,
244 ssl_addr: options.local_ssl.unwrap_or(options.local),
245 nonssl_addr: options.local,
246 use_quic: options.quic,
247 ssh_cmd: options.ssh_cmd.clone(),
248 token: options.token,
249
250 serve_files: options.serve_files,
251 files_index: options.files_index,
252 consts: consts.clone()
253 };
254
255 let tunnel_id = packet.tunnel_id;
256 tokio::task::spawn(async move {
257 let res = if packet.ssh {
258 spawn_ssh_tunnel(
259 opener,
260 settings,
261 requested_time,
262 tunnel_id
263 )
264 .await
265 } else {
266 spawn_tunnel(
267 opener,
268 settings,
269 packet.ssl,
270 requested_time,
271 tunnel_id
272 )
273 .await
274 };
275
276 if let Err(e) = res {
277 tracing::error!("Tunnel Error: {e}");
278 }
279 });
280 }
281 _ = last_ping.tick() => {
282 tracing::error!("No ping for 30s! Closing connector");
283 return Ok(());
284 }
285 }
286 }
287}
288
289async fn establish_connection(
290 opener: Arc<ConnectionOpener>,
291 token: u128,
292 tunnel_id: u128,
293 settings: &TunnelSettings,
294 request_time: Instant,
295) -> Result<ConnectorStream> {
296 if request_time.elapsed().as_millis() > settings.consts.max_req_time {
297 return Err(anyhow!("Requested time exceeded max request time."));
298 }
299
300 let (mut tunnel_stream, nonce) = if settings.use_quic {
301 let connection = opener
302 .quic_connection
303 .as_ref()
304 .ok_or(anyhow::anyhow!("Quic Connection ref get"))?;
305
306 let quic_bi = connection
307 .open_bi()
308 .await
309 .map_err(|e| anyhow!("failed to open stream: {}", e))?;
310
311 let stream_id_bytes = u64::from(quic_bi.0.id()).to_le_bytes();
312 let mut nonce = [0u8; 32];
313 connection
314 .export_keying_material(&mut nonce, b"fkm-proxy-auth", &stream_id_bytes)
315 .map_err(|_| anyhow!("Failed to export QUIC session keying material"))?;
316
317 (ConnectorStream::Quic(quic_bi), nonce)
318 } else {
319 let stream = TcpStream::connect(settings.proxy_addr).await?;
320 stream.set_nodelay(true)?;
321 let stream = opener
322 .tls_connector
323 .connect(
324 rustls::pki_types::ServerName::try_from("proxy.lan")?,
325 stream,
326 )
327 .await?;
328
329 let nonce: [u8; 32] =
330 stream
331 .get_ref()
332 .1
333 .export_keying_material([0u8; 32], b"fkm-proxy-auth", None)?;
334
335 (ConnectorStream::TcpTlsClient(Box::new(stream)), nonce)
336 };
337
338 let hello_packet = HelloPacket {
339 hp_type: HelloPacketType::Tunnel,
340 token_hmac: compute_token_hmac(token, &nonce),
341 own_ssl: false,
342 redirect_ssl: false,
343 ssh_enabled: false,
344 tunnel_id,
345 version: get_version(),
346 };
347 tunnel_stream.write_all(&hello_packet.to_buf()).await?;
348 Ok(tunnel_stream)
349}
350
351async fn spawn_tunnel(
352 opener: Arc<ConnectionOpener>,
353 settings: TunnelSettings,
354 ssl: bool,
355 request_time: Instant,
356 tunnel_id: u128,
357) -> Result<()> {
358 let mut tunnel_stream =
359 establish_connection(opener, settings.token, tunnel_id, &settings, request_time).await?;
360
361 if settings.serve_files {
362 _ = super::serve::serve_files(&mut tunnel_stream, settings.files_index, &settings.consts)
363 .await;
364 tunnel_stream.flush().await?;
365 _ = tunnel_stream.shutdown().await;
366 return Ok(());
367 }
368
369 let local_addr = match ssl {
370 true => settings.ssl_addr,
371 false => settings.nonssl_addr,
372 };
373
374 let Ok(mut local_stream) = TcpStream::connect(local_addr).await else {
375 write_http_resp(
376 &mut tunnel_stream,
377 500,
378 &settings
379 .consts
380 .error_html
381 .replace("{MSG}", "Local server not running!"),
382 "text/html",
383 )
384 .await?;
385 _ = tunnel_stream.shutdown().await;
386
387 return Ok(());
388 };
389
390 local_stream.set_nodelay(true)?;
391 _ = tokio::io::copy_bidirectional(&mut local_stream, &mut tunnel_stream).await;
392 _ = local_stream.shutdown().await;
393 _ = tunnel_stream.shutdown().await;
394 Ok(())
395}
396
397async fn spawn_ssh_tunnel(
398 opener: Arc<ConnectionOpener>,
399 settings: TunnelSettings,
400 request_time: Instant,
401 tunnel_id: u128,
402) -> Result<()> {
403 let Some(ref ssh_cmd) = settings.ssh_cmd else {
404 return Err(anyhow!("Ssh tunnel not enabled by client!"));
405 };
406
407 let mut tunnel_stream =
408 establish_connection(opener, settings.token, tunnel_id, &settings, request_time).await?;
409
410 let mut header_buf = [0; SshPacketHeader::HEADER_LENGTH];
411 let mut buf = [0u8; 4096];
412 tunnel_stream.read_exact(&mut header_buf).await?;
413 let header = SshPacketHeader::from_buf(&header_buf);
414
415 let tunnel_user = if header.packet_type == SshPacketType::User {
416 tunnel_stream
417 .read_exact(&mut buf[..header.length as usize])
418 .await?;
419
420 core::str::from_utf8(&buf[..header.length as usize])?
421 } else {
422 return Err(anyhow!("Wrong first ssh tunnel packet!"));
423 };
424
425 let (mut pty, pts) = pty_process::open()?;
426 let cmd = pty_process::Command::new(ssh_cmd).env("PROXY_USER", tunnel_user);
427 let mut child = cmd.spawn(pts)?;
428
429 loop {
430 tokio::select! {
431 recv = tunnel_stream.read_exact(&mut header_buf) => {
432 if let Ok(n) = recv {
433 if n == 0 {
434 break;
435 }
436
437 let header = SshPacketHeader::from_buf(&header_buf);
438 if header.length > 4096 {
439 break;
440 }
441
442 match header.packet_type {
443 crate::utils::ssh::SshPacketType::PtyResize => {
444 let rows = tunnel_stream.read_u16().await?;
445 let cols = tunnel_stream.read_u16().await?;
446 pty.resize(pty_process::Size::new(rows, cols))?;
447 },
448 crate::utils::ssh::SshPacketType::Data => {
449 tunnel_stream.read_exact(&mut buf[..header.length as usize]).await?;
450 pty.write_all(&buf[..header.length as usize]).await?;
451
452 },
453 _ => {}
454 }
455 }
456 }
457 res = pty.read(&mut buf) => {
458 if let Ok(n) = res {
459 if n == 0 {
460 break;
461 }
462
463 tunnel_stream
464 .write_all(
465 &SshPacketHeader {
466 packet_type: super::ssh::SshPacketType::Data,
467 length: n as u32,
468 }
469 .to_buf(),
470 )
471 .await?;
472 tunnel_stream.write_all(&buf[..n]).await?;
473 } else {
474 break;
475 }
476 }
477 p_res = child.wait() => {
478 if p_res.is_err() {
479 break;
480 }
481
482 let _p_res = p_res?;
483 break;
484 }
485 }
486 }
487
488 _ = tunnel_stream.shutdown().await;
489 Ok(())
490}