Russh just pipe stdin and stdout to remote host

Hey Guys I am very new to Rust and I am trying to connect to a remote host via ssh
and just pipe all stdin and stdout between client and remote. This is my current approach that is not working at all, there is no output or input:

// Starts the ssh bridge driver
async fn start_ssh_driver(host: String, user: String, private_key_path: String, run_command: String) -> Result<(), anyhow::Error> {
    
    // Open SSH Session
    let key_pair = load_secret_key(private_key_path, None)?;
    let config = client::Config {
        connection_timeout: Some(Duration::from_secs(5)),
        ..<_>::default()
    };
    let config = Arc::new(config);
    let sh = Client {};
    let mut session = client::connect(config, SocketAddr::from_str(&host).unwrap(), sh).await?;
    let _auth_res = session
        .authenticate_publickey(user, Arc::new(key_pair))
        .await?;
    
    // Create new channel
    let channel = session.channel_open_session().await.unwrap();
    let mut stream = channel.into_stream();

    // First Command
    let mut first_com = run_command;
    first_com.push_str("\n");
    stream.write_all(&first_com.as_bytes()).await.unwrap();


    // Start async stuff
    let stdin = stdin();
    let mut reader = BufReader::new(stdin);

    let mut line_in= String::new();
    let mut line_out = String::new();

    match tokio::spawn(async move {
        loop {
            tokio::select! {
                res = stream.read_to_string(&mut line_out) => {
                    match res {
                        Err(_) => break,
                        _ => println!("{}", line_out),
                    }
                },
                res = reader.read_line(&mut line_in) => {
                    match res {
                        Err(_) => break,
                        _ => stream.write_all(&line_in.as_bytes()).await.unwrap(),
                    }
                }
            }
        }
        anyhow::Ok::<()>(())
    }).await? {
        _ => {},
    }

   return Ok(());
}

struct Client {}

#[async_trait]
impl client::Handler for Client {
    type Error = anyhow::Error;
 
    async fn check_server_key(
        self,
        _server_public_key: &key::PublicKey,
    ) -> Result<(Self, bool), Self::Error> {
        Ok((self, true))
    }
 }

Does any has a hint why its not working, I double checked the certificate, that should not be the issue...

Could you tell us which ssh implementation you are using? Is there a reason why you use async? Have you looked at the ssh2 crate? Running a command and printing the result looks quite easy with ssh2 (though it is sync).

EDIT: my bad, the crate you are using is russh from your title :slightly_smiling_face:

1 Like

Thanks for your reply. The challenge is that I need to async send and receive data. I try to stream output from a chess engine but need to let the user interrupt the chess engine by being able to send "stop" at any point.

I'm only guessing here, but my first instinct would be to simplify this. Maybe instead of select! (which according to the documentation drops the future that doesn't resolve first, which is not what you want) spawn two tasks, one for receiving from the ssl channel and one for receiving input from stdin and sending it to the remote host?

I tried that before but I had following code:

let mut channel: Arc<Channel<Msg>> = Arc::new(self.session.channel_open_session().await.unwrap());
        channel.exec(false, command).await?;

        let mut in_stream: ReceiverStream<Vec<u8>> = ReceiverStream::new(stdin);
        tokio::spawn(async move {
            while let Some(item) = in_stream.next().await {
                match str::from_utf8(&item) {
                    Ok(v) => channel.exec(false, v).await.unwrap(),
                    Err(_) => {/* Not handled :) */},
                };
            }
        });

        while let Some(msg) = channel.wait().await {
            match msg {
                russh::ChannelMsg::Data { ref data } => {
                    let output = Vec::new();
                    output.write_all(data).unwrap();
                    self.stdout.send(output).await.unwrap();
                }
                _ => {}
            }
        }
        Ok(())

But this gives me the error: cannot borrow data in an Arc as mutable. And I need to borough it to the tokio task. Am I missing something ?

If you need interior mutability inside an Arc, you can use a Mutex or RwLock, like Arc<Mutex<Channel>>.

RwLock could work! I was using mutex to, but the issue was that it is then blocking either on read or write and if nothing is received you can also not send or wise versa. RwLock is worth a try! Thank you! :slight_smile:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.