use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tower_service::Service; use hyper_util::client::legacy::connect::proxy::{SocksV4, SocksV5, Tunnel}; use hyper_util::client::legacy::connect::HttpConnector; #[cfg(not(miri))] #[tokio::test] async fn test_tunnel_works() { let tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let addr = tcp.local_addr().expect("local_addr"); let proxy_dst = format!("http://{addr}").parse().expect("uri"); let mut connector = Tunnel::new(proxy_dst, HttpConnector::new()); let t1 = tokio::spawn(async move { let _conn = connector .call("https://hyper.rs".parse().unwrap()) .await .expect("tunnel"); }); let t2 = tokio::spawn(async move { let (mut io, _) = tcp.accept().await.expect("accept"); let mut buf = [0u8; 64]; let n = io.read(&mut buf).await.expect("read 1"); assert_eq!( &buf[..n], b"CONNECT hyper.rs:443 HTTP/1.1\r\nHost: hyper.rs:443\r\n\r\n" ); io.write_all(b"HTTP/1.1 200 OK\r\n\r\n") .await .expect("write 1"); }); t1.await.expect("task 1"); t2.await.expect("task 2"); } #[cfg(not(miri))] #[tokio::test] async fn test_socks_v5_without_auth_works() { let proxy_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let proxy_addr = proxy_tcp.local_addr().expect("local_addr"); let proxy_dst = format!("http://{proxy_addr}").parse().expect("uri"); let target_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let target_addr = target_tcp.local_addr().expect("local_addr"); let target_dst = format!("http://{target_addr}").parse().expect("uri"); let mut connector = SocksV5::new(proxy_dst, HttpConnector::new()); // Client // // Will use `SocksV5` to establish proxy tunnel. // Will send "Hello World!" to the target and receive "Goodbye!" back. let t1 = tokio::spawn(async move { let conn = connector.call(target_dst).await.expect("tunnel"); let mut tcp = conn.into_inner(); tcp.write_all(b"Hello World!").await.expect("write 1"); let mut buf = [0u8; 64]; let n = tcp.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], b"Goodbye!"); }); // Proxy // // Will receive CONNECT command from client. // Will connect to target and success code back to client. // Will blindly tunnel between client and target. let t2 = tokio::spawn(async move { let (mut to_client, _) = proxy_tcp.accept().await.expect("accept"); let mut buf = [0u8; 513]; // negotiation req/res let n = to_client.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], [0x05, 0x01, 0x00]); to_client.write_all(&[0x05, 0x00]).await.expect("write 1"); // command req/rs let [p1, p2] = target_addr.port().to_be_bytes(); let [ip1, ip2, ip3, ip4] = [0x7f, 0x00, 0x00, 0x01]; let message = [0x05, 0x01, 0x00, 0x01, ip1, ip2, ip3, ip4, p1, p2]; let n = to_client.read(&mut buf).await.expect("read 2"); assert_eq!(&buf[..n], message); let mut to_target = TcpStream::connect(target_addr).await.expect("connect"); let message = [0x05, 0x00, 0x00, 0x01, ip1, ip2, ip3, ip4, p1, p2]; to_client.write_all(&message).await.expect("write 2"); let (from_client, from_target) = tokio::io::copy_bidirectional(&mut to_client, &mut to_target) .await .expect("proxy"); assert_eq!(from_client, 12); assert_eq!(from_target, 8) }); // Target server // // Will accept connection from proxy server // Will receive "Hello World!" from the client and return "Goodbye!" let t3 = tokio::spawn(async move { let (mut io, _) = target_tcp.accept().await.expect("accept"); let mut buf = [0u8; 64]; let n = io.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], b"Hello World!"); io.write_all(b"Goodbye!").await.expect("write 1"); }); t1.await.expect("task - client"); t2.await.expect("task - proxy"); t3.await.expect("task - target"); } #[cfg(not(miri))] #[tokio::test] async fn test_socks_v5_with_auth_works() { let proxy_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let proxy_addr = proxy_tcp.local_addr().expect("local_addr"); let proxy_dst = format!("http://{proxy_addr}").parse().expect("uri"); let target_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let target_addr = target_tcp.local_addr().expect("local_addr"); let target_dst = format!("http://{target_addr}").parse().expect("uri"); let mut connector = SocksV5::new(proxy_dst, HttpConnector::new()).with_auth("user".into(), "pass".into()); // Client // // Will use `SocksV5` to establish proxy tunnel. // Will send "Hello World!" to the target and receive "Goodbye!" back. let t1 = tokio::spawn(async move { let conn = connector.call(target_dst).await.expect("tunnel"); let mut tcp = conn.into_inner(); tcp.write_all(b"Hello World!").await.expect("write 1"); let mut buf = [0u8; 64]; let n = tcp.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], b"Goodbye!"); }); // Proxy // // Will receive CONNECT command from client. // Will connect to target and success code back to client. // Will blindly tunnel between client and target. let t2 = tokio::spawn(async move { let (mut to_client, _) = proxy_tcp.accept().await.expect("accept"); let mut buf = [0u8; 513]; // negotiation req/res let n = to_client.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], [0x05, 0x01, 0x02]); to_client.write_all(&[0x05, 0x02]).await.expect("write 1"); // auth req/res let n = to_client.read(&mut buf).await.expect("read 2"); let [u1, u2, u3, u4] = b"user"; let [p1, p2, p3, p4] = b"pass"; let message = [0x01, 0x04, *u1, *u2, *u3, *u4, 0x04, *p1, *p2, *p3, *p4]; assert_eq!(&buf[..n], message); to_client.write_all(&[0x01, 0x00]).await.expect("write 2"); // command req/res let n = to_client.read(&mut buf).await.expect("read 3"); let [p1, p2] = target_addr.port().to_be_bytes(); let [ip1, ip2, ip3, ip4] = [0x7f, 0x00, 0x00, 0x01]; let message = [0x05, 0x01, 0x00, 0x01, ip1, ip2, ip3, ip4, p1, p2]; assert_eq!(&buf[..n], message); let mut to_target = TcpStream::connect(target_addr).await.expect("connect"); let message = [0x05, 0x00, 0x00, 0x01, ip1, ip2, ip3, ip4, p1, p2]; to_client.write_all(&message).await.expect("write 3"); let (from_client, from_target) = tokio::io::copy_bidirectional(&mut to_client, &mut to_target) .await .expect("proxy"); assert_eq!(from_client, 12); assert_eq!(from_target, 8) }); // Target server // // Will accept connection from proxy server // Will receive "Hello World!" from the client and return "Goodbye!" let t3 = tokio::spawn(async move { let (mut io, _) = target_tcp.accept().await.expect("accept"); let mut buf = [0u8; 64]; let n = io.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], b"Hello World!"); io.write_all(b"Goodbye!").await.expect("write 1"); }); t1.await.expect("task - client"); t2.await.expect("task - proxy"); t3.await.expect("task - target"); } #[cfg(not(miri))] #[tokio::test] async fn test_socks_v5_with_server_resolved_domain_works() { let proxy_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let proxy_addr = proxy_tcp.local_addr().expect("local_addr"); let proxy_addr = format!("http://{proxy_addr}").parse().expect("uri"); let mut connector = SocksV5::new(proxy_addr, HttpConnector::new()) .with_auth("user".into(), "pass".into()) .local_dns(false); // Client // // Will use `SocksV5` to establish proxy tunnel. // Will send "Hello World!" to the target and receive "Goodbye!" back. let t1 = tokio::spawn(async move { let _conn = connector .call("https://hyper.rs:443".try_into().unwrap()) .await .expect("tunnel"); }); // Proxy // // Will receive CONNECT command from client. // Will connect to target and success code back to client. // Will blindly tunnel between client and target. let t2 = tokio::spawn(async move { let (mut to_client, _) = proxy_tcp.accept().await.expect("accept"); let mut buf = [0u8; 513]; // negotiation req/res let n = to_client.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], [0x05, 0x01, 0x02]); to_client.write_all(&[0x05, 0x02]).await.expect("write 1"); // auth req/res let n = to_client.read(&mut buf).await.expect("read 2"); let [u1, u2, u3, u4] = b"user"; let [p1, p2, p3, p4] = b"pass"; let message = [0x01, 0x04, *u1, *u2, *u3, *u4, 0x04, *p1, *p2, *p3, *p4]; assert_eq!(&buf[..n], message); to_client.write_all(&[0x01, 0x00]).await.expect("write 2"); // command req/res let n = to_client.read(&mut buf).await.expect("read 3"); let host = "hyper.rs"; let port: u16 = 443; let mut message = vec![0x05, 0x01, 0x00, 0x03, host.len() as u8]; message.extend(host.bytes()); message.extend(port.to_be_bytes()); assert_eq!(&buf[..n], message); let mut message = vec![0x05, 0x00, 0x00, 0x03, host.len() as u8]; message.extend(host.bytes()); message.extend(port.to_be_bytes()); to_client.write_all(&message).await.expect("write 3"); }); t1.await.expect("task - client"); t2.await.expect("task - proxy"); } #[cfg(not(miri))] #[tokio::test] async fn test_socks_v5_with_locally_resolved_domain_works() { let proxy_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let proxy_addr = proxy_tcp.local_addr().expect("local_addr"); let proxy_addr = format!("http://{proxy_addr}").parse().expect("uri"); let mut connector = SocksV5::new(proxy_addr, HttpConnector::new()) .with_auth("user".into(), "pass".into()) .local_dns(true); // Client // // Will use `SocksV5` to establish proxy tunnel. // Will send "Hello World!" to the target and receive "Goodbye!" back. let t1 = tokio::spawn(async move { let _conn = connector .call("https://hyper.rs:443".try_into().unwrap()) .await .expect("tunnel"); }); // Proxy // // Will receive CONNECT command from client. // Will connect to target and success code back to client. // Will blindly tunnel between client and target. let t2 = tokio::spawn(async move { let (mut to_client, _) = proxy_tcp.accept().await.expect("accept"); let mut buf = [0u8; 513]; // negotiation req/res let n = to_client.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], [0x05, 0x01, 0x02]); to_client.write_all(&[0x05, 0x02]).await.expect("write 1"); // auth req/res let n = to_client.read(&mut buf).await.expect("read 2"); let [u1, u2, u3, u4] = b"user"; let [p1, p2, p3, p4] = b"pass"; let message = [0x01, 0x04, *u1, *u2, *u3, *u4, 0x04, *p1, *p2, *p3, *p4]; assert_eq!(&buf[..n], message); to_client.write_all(&[0x01, 0x00]).await.expect("write 2"); // command req/res let n = to_client.read(&mut buf).await.expect("read 3"); let message = [0x05, 0x01, 0x00]; assert_eq!(&buf[..3], message); assert!(buf[3] == 0x01 || buf[3] == 0x04); // IPv4 or IPv6 assert_eq!(n, 4 + 4 * (buf[3] as usize) + 2); let message = vec![0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]; to_client.write_all(&message).await.expect("write 3"); }); t1.await.expect("task - client"); t2.await.expect("task - proxy"); } #[cfg(not(miri))] #[tokio::test] async fn test_socks_v4_works() { let proxy_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let proxy_addr = proxy_tcp.local_addr().expect("local_addr"); let proxy_dst = format!("http://{proxy_addr}").parse().expect("uri"); let target_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let target_addr = target_tcp.local_addr().expect("local_addr"); let target_dst = format!("http://{target_addr}").parse().expect("uri"); let mut connector = SocksV4::new(proxy_dst, HttpConnector::new()); // Client // // Will use `SocksV4` to establish proxy tunnel. // Will send "Hello World!" to the target and receive "Goodbye!" back. let t1 = tokio::spawn(async move { let conn = connector.call(target_dst).await.expect("tunnel"); let mut tcp = conn.into_inner(); tcp.write_all(b"Hello World!").await.expect("write 1"); let mut buf = [0u8; 64]; let n = tcp.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], b"Goodbye!"); }); // Proxy // // Will receive CONNECT command from client. // Will connect to target and success code back to client. // Will blindly tunnel between client and target. let t2 = tokio::spawn(async move { let (mut to_client, _) = proxy_tcp.accept().await.expect("accept"); let mut buf = [0u8; 512]; let [p1, p2] = target_addr.port().to_be_bytes(); let [ip1, ip2, ip3, ip4] = [127, 0, 0, 1]; let message = [4, 0x01, p1, p2, ip1, ip2, ip3, ip4, 0, 0]; let n = to_client.read(&mut buf).await.expect("read"); assert_eq!(&buf[..n], message); let mut to_target = TcpStream::connect(target_addr).await.expect("connect"); let message = [0, 90, p1, p2, ip1, ip2, ip3, ip4]; to_client.write_all(&message).await.expect("write"); let (from_client, from_target) = tokio::io::copy_bidirectional(&mut to_client, &mut to_target) .await .expect("proxy"); assert_eq!(from_client, 12); assert_eq!(from_target, 8) }); // Target server // // Will accept connection from proxy server // Will receive "Hello World!" from the client and return "Goodbye!" let t3 = tokio::spawn(async move { let (mut io, _) = target_tcp.accept().await.expect("accept"); let mut buf = [0u8; 64]; let n = io.read(&mut buf).await.expect("read 1"); assert_eq!(&buf[..n], b"Hello World!"); io.write_all(b"Goodbye!").await.expect("write 1"); }); t1.await.expect("task - client"); t2.await.expect("task - proxy"); t3.await.expect("task - target"); } #[cfg(not(miri))] #[tokio::test] async fn test_socks_v5_optimistic_works() { let proxy_tcp = TcpListener::bind("127.0.0.1:0").await.expect("bind"); let proxy_addr = proxy_tcp.local_addr().expect("local_addr"); let proxy_dst = format!("http://{proxy_addr}").parse().expect("uri"); let target_addr = std::net::SocketAddr::new([127, 0, 0, 1].into(), 1234); let target_dst = format!("http://{target_addr}").parse().expect("uri"); let mut connector = SocksV5::new(proxy_dst, HttpConnector::new()) .with_auth("ABC".into(), "XYZ".into()) .send_optimistically(true); // Client // // Will use `SocksV5` to establish proxy tunnel. // Will send "Hello World!" to the target and receive "Goodbye!" back. let t1 = tokio::spawn(async move { let _ = connector.call(target_dst).await.expect("tunnel"); }); // Proxy // // Will receive SOCKS handshake from client. // Will connect to target and success code back to client. // Will blindly tunnel between client and target. let t2 = tokio::spawn(async move { let (mut to_client, _) = proxy_tcp.accept().await.expect("accept"); let [p1, p2] = target_addr.port().to_be_bytes(); let mut buf = [0; 22]; let request = vec![ 5, 1, 2, // Negotiation 1, 3, 65, 66, 67, 3, 88, 89, 90, // Auth ("ABC"/"XYZ") 5, 1, 0, 1, 127, 0, 0, 1, p1, p2, // Reply ]; let response = vec![ 5, 2, // Negotiation, 1, 0, // Auth, 5, 0, 0, 1, 127, 0, 0, 1, p1, p2, // Reply ]; // Accept all handshake messages to_client.read_exact(&mut buf).await.expect("read"); assert_eq!(request.as_slice(), buf); // Send all handshake messages back to_client .write_all(response.as_slice()) .await .expect("write"); to_client.flush().await.expect("flush"); }); t1.await.expect("task - client"); t2.await.expect("task - proxy"); }