1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use std::io::Error as IoError;
use std::process::{Child, Command};

#[derive(Debug)]
pub enum SshError {
    IoError(IoError),
}

#[derive(Debug)]
pub struct Tunnel {
    ssh_host: String,
    ssh_port: u64,
    session: Option<Child>,
}

impl Tunnel {
    pub fn new(ssh_host: &str, ssh_port: u64) -> Self {
        Tunnel {
            ssh_host: ssh_host.to_string(),
            ssh_port,
            session: None,
        }
    }

    pub fn open(&mut self, local_port: u64, remote_port: u64) -> Result<(), SshError> {
        match Command::new("ssh")
            .arg(&self.ssh_host)
            .arg("-qN")
            .arg(format!("-p {}", self.ssh_port))
            .arg(format!("-L {}:localhost:{}", local_port, remote_port))
            .spawn()
        {
            Ok(child) => {
                self.session = Some(child);
                Ok(())
            }
            Err(error) => Err(SshError::IoError(error)),
        }
    }

    pub fn close(mut self) -> Result<(), SshError> {
        match self.session {
            None => Ok(()),
            Some(mut session) => match session.kill() {
                Ok(_) => {
                    self.session = None;
                    Ok(())
                }
                Err(error) => Err(SshError::IoError(error)),
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{SshError, Tunnel};
    use std::thread::sleep;
    use std::time::Duration;

    #[test]
    fn it_works() -> Result<(), SshError> {
        let mut tunnel = Tunnel::new("localhost", 22);
        tunnel.open(8085, 80)?;

        // Check htop, filter for 85:localhost:80.
        sleep(Duration::from_secs(5));

        tunnel.close()
    }
}