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