use anyhow::Result;
use reverse_ssh::{ReverseSshClient, ReverseSshConfig};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use tracing_subscriber;
async fn start_simple_http_server(port: u16) -> Result<()> {
let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?;
println!("[HTTP Server] Listening on http://127.0.0.1:{}", port);
loop {
let (mut socket, addr) = listener.accept().await?;
println!("[HTTP Server] Connection from {}", addr);
tokio::spawn(async move {
let mut buffer = [0; 1024];
match socket.read(&mut buffer).await {
Ok(n) if n > 0 => {
let request = String::from_utf8_lossy(&buffer[..n]);
println!("[HTTP Server] Request: {}", request.lines().next().unwrap_or(""));
let response = format!(
"HTTP/1.1 200 OK\r\n\
Content-Type: text/html; charset=utf-8\r\n\
Connection: close\r\n\
\r\n\
<!DOCTYPE html>\
<html>\
<head><title>Reverse SSH Test</title></head>\
<body>\
<h1>🚀 Reverse SSH Tunnel Working!</h1>\
<p>This page is served from localhost:8080</p>\
<p>Accessed through reverse SSH tunnel</p>\
<p>Time: {}</p>\
</body>\
</html>",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
);
let _ = socket.write_all(response.as_bytes()).await;
}
_ => {}
}
});
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
println!("=== Reverse SSH Tunnel - Local Test ===\n");
let ssh_host = std::env::var("SSH_HOST")
.unwrap_or_else(|_| {
eprintln!("Error: SSH_HOST environment variable not set");
eprintln!("\nUsage:");
eprintln!(" export SSH_HOST=your-server.com");
eprintln!(" export SSH_USER=your-username");
eprintln!(" export SSH_KEY=~/.ssh/id_rsa");
eprintln!(" export REMOTE_PORT=9999 # optional, default 9999");
eprintln!(" export LOCAL_PORT=8080 # optional, default 8080");
eprintln!(" cargo run --example local_test");
std::process::exit(1);
});
let ssh_user = std::env::var("SSH_USER")
.unwrap_or_else(|_| {
eprintln!("Error: SSH_USER environment variable not set");
std::process::exit(1);
});
let ssh_key = std::env::var("SSH_KEY")
.ok();
let ssh_pass = std::env::var("SSH_PASS")
.ok();
if ssh_key.is_none() && ssh_pass.is_none() {
eprintln!("Error: Either SSH_KEY or SSH_PASS must be set");
std::process::exit(1);
}
let remote_port: u32 = std::env::var("REMOTE_PORT")
.unwrap_or_else(|_| "9999".to_string())
.parse()?;
let local_port: u16 = std::env::var("LOCAL_PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()?;
println!("Configuration:");
println!(" SSH Server: {}", ssh_host);
println!(" SSH User: {}", ssh_user);
println!(" Authentication: {}", if ssh_key.is_some() { "Private Key" } else { "Password" });
println!(" Remote Port: {}", remote_port);
println!(" Local Port: {}\n", local_port);
tokio::spawn(async move {
if let Err(e) = start_simple_http_server(local_port).await {
eprintln!("HTTP server error: {}", e);
}
});
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let config = ReverseSshConfig {
server_addr: ssh_host.clone(),
server_port: 22,
username: ssh_user,
key_path: ssh_key,
password: ssh_pass,
bind_address: String::new(),
remote_port,
local_addr: "127.0.0.1".to_string(),
local_port,
};
println!("Starting reverse SSH tunnel...");
println!("\nOnce connected, access your service at:");
println!(" http://{}:{}\n", ssh_host, remote_port);
println!("Press Ctrl+C to stop.\n");
let mut client = ReverseSshClient::new(config);
client.run().await?;
Ok(())
}