Trying to wrap my head around thrussh/tokio with child process

I want to write an ssh server which can run a child process and pipe data to/from it. As a prototype, I'm looking to be able to do this:

  1. Start the server.
  2. Run:
    $ echo "hello world" | ssh -p 2222 127.0.0.1 'wc'
    

This should work just like echo "hello world" | wc, except it will run wc from the ssh server and use pipes to send data between the ssh server and the child process, and finally return the output to the client's ssh client and output it to the terminal.

I'm using thrussh for this, which is a tokio-based ssh framework, but I'm having some trouble wrapping my head around some concepts in thrussh, but I'm also unsure about some of the semantics regarding piping to a child process using tokio's version of Command.

The server does seem to work in the sense that the client connects to it, it receives the command and data, and from what I can tell the data is passed to wc, which returns its output -- however, the server fails when trying to write the result back to the client. The h.data() returns an error, but the error seems to only describes the properties of the output buffer; I can't see it including any hint of what's actually wrong.

Does anyone know what I'm missing?

One of the things which is throwing me for a loop is that thrussh is an async crate, but the actual callbacks aren't async. (I would assume because there's no native support for async trait methods yet). This has caused me to introduce a channel, merely for bridging the non-async and the async world (see child_stdin_tx). Is there a way to avoid having this?

You can run async code in the handlers by returning a future. To do that, specify the appropriate future type in the trait to be

use futures::future::BoxFuture; // import type alias
type FutureUnit = BoxFuture<'static, Result<(Self, Session), Self::Error>>;

Then you will be able to return a future from the non-async functions:

fn exec_request(...) -> Self::FutureUnit {
    // prepare variables for creation of future
    Box::pin(async move {
        // your async code here
    })
}

Note that you cannot put any kind of reference inside the future, so you will need to make clones of everything that the future needs to access if you only have a reference to it.

2 Likes

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.