1use anyhow::Context as _;
2use tokio::{
3 io::{AsyncRead, AsyncWrite, AsyncWriteExt as _, copy_bidirectional},
4 net::TcpStream,
5};
6
7pub struct MasqueradeConnection<T> {
9 inner: T,
10}
11
12impl<T> MasqueradeConnection<T>
13where
14 T: AsyncRead + AsyncWrite + Unpin,
15{
16 pub fn new(ts: T) -> Self {
18 Self { inner: ts }
19 }
20
21 pub async fn handle(mut self) -> anyhow::Result<()> {
23 let stream = &mut self.inner;
24 let local_addr: Option<String> = None;
25
26 match local_addr {
27 Some(ref local_addr) => {
28 info!("requested proxy local {}", local_addr);
29 let mut local_ts = TcpStream::connect(local_addr)
30 .await
31 .context(format!("proxy can't connect addr {}", local_addr))?;
32 debug!("proxy connect success {:?}", local_addr);
33
34 copy_bidirectional(stream, &mut local_ts).await.ok();
35 }
36 None => {
37 info!("response not found");
38 Self::resp_html(stream).await
39 }
40 }
41
42 Ok(())
43 }
44 async fn resp_html(stream: &mut T) {
45 stream
46 .write_all(
47 &b"HTTP/1.0 404 Not Found\r\n\
48 Content-Type: text/plain; charset=utf-8\r\n\
49 Content-length: 13\r\n\r\n\
50 404 Not Found"[..],
51 )
52 .await
53 .unwrap();
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use tokio::io::AsyncReadExt;
60
61 use super::MasqueradeConnection;
62
63 #[tokio::test]
64 async fn handle_without_local_target_returns_404_response() {
65 let (client, mut peer) = tokio::io::duplex(256);
66
67 let task = tokio::spawn(async move { MasqueradeConnection::new(client).handle().await });
68
69 let mut buf = Vec::new();
70 peer.read_to_end(&mut buf).await.unwrap();
71 task.await.unwrap().unwrap();
72
73 let response = String::from_utf8(buf).unwrap();
74 assert!(response.starts_with("HTTP/1.0 404 Not Found\r\n"));
75 assert!(response.contains("Content-Type: text/plain; charset=utf-8\r\n"));
76 assert!(response.ends_with("404 Not Found"));
77 }
78}