Skip to main content

fkm_proxy/utils/
client.rs

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; // ping/pong
234                } 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}