TCP tunnel through ssh in rust

I am trying to write a small program in Rust to accomplish basically what ssh -L 5000:localhost:8080 : set up a tunnel between localhost:5000 on my computer and localhost:8080 on the remote machine so that when starting the HTTP server on port 8080 on the remote control, I can access it through the local network through localhost:5000 , bypassing the remote firewall, which can block external access to 8080.

I understand that ssh already does just that and is reliable, this is a training project, plus I can add some functionality if I earn it :) This is an empty game (without streaming processing, without errors) that I came up with so far (should compile on Rust 1.8):

 extern crate ssh2; // see http://alexcrichton.com/ssh2-rs/ use std::io::Read; use std::io::Write; use std::str; use std::net; fn main() { // establish SSH session with remote host println!("Connecting to host..."); // substitute appropriate value for IPv4 let tcp = net::TcpStream::connect("<IPv4>:22").unwrap(); let mut session = ssh2::Session::new().unwrap(); session.handshake(&tcp).unwrap(); // substitute appropriate values for username and password // session.userauth_password("<username>", "<password>").unwrap(); assert!(session.authenticated()); println!("SSH session authenticated."); // start listening for TCP connections let listener = net::TcpListener::bind("localhost:5000").unwrap(); println!("Started listening, ready to accept"); for stream in listener.incoming() { println!("==============================================================================="); // read the incoming request let mut stream = stream.unwrap(); let mut request = vec![0; 8192]; let read_bytes = stream.read(&mut request).unwrap(); println!("REQUEST ({} BYTES):\n{}", read_bytes, str::from_utf8(&request).unwrap()); // send the incoming request over ssh on to the remote localhost and port // where an HTTP server is listening let mut channel = session.channel_direct_tcpip("localhost", 8080, None).unwrap(); channel.write(&request).unwrap(); // read the remote server response (all of it, for simplicity sake) // and forward it to the local TCP connection stream let mut response = Vec::new(); let read_bytes = channel.read_to_end(&mut response).unwrap(); stream.write(&response).unwrap(); println!("SENT {} BYTES AS RESPONSE", read_bytes); }; } 

As it turned out, this kind of work, but not quite. For instance. if the application running on the remote server is Cloud9 IDE Core / SDK , the main HTML page and some resources are loaded, but requests for other resources ( .js , .css ) are systematically returned empty (regardless of whether it is requested on the main page or directly), i.e. nothing is read in the call until channel.read_to_end() . Other (simpler?) Web apps or static sites seem to work fine. Actually, when using ssh -L 5000:localhost:8080 even Cloud9 Core works fine.

I expect other more complex applications to be affected as well. I see various potential areas where the error may be hiding in my code:

  • Rust stream read / write APIs: maybe the call to channel.read_to_end() works differently than I think, and just accidentally does the right thing for some kind of request?
  • HTTP: maybe I need to bother with HTTP headers before sending a request to a remote server? or maybe I will give the response flow soon, just by calling channel.read_to_end() ?
  • Rust itself is my first relatively serious attempt to learn a system programming language.

I already tried playing with some of the above, but I will be grateful for any suggestions on ways to explore, preferably along with an explanation of why this might be a problem :)

+6
source share
1 answer

tl; dr : Use Go and its network libraries for this specific task.

It turns out that my very rudimentary understanding of how HTTP works might be a mistake here (initially I thought I could just pop the data back and forth over the ssh connection, but I couldn’t achieve this - if someone knows a way to do this, I'm still curious!). See some suggestions in the comments, but basically it comes down to the intricacies of how HTTP connections are initiated, maintained, and closed. I tried using the hyper mailbox to distract this data, but it turns out that the ssh2 mailbox (as well as the base libssh2) is not thread safe, which makes it impossible to use the ssh session in hyper handlers.

At this stage, I decided that there is no simple, high-level way for a beginner to achieve this in Rust (first I would have to do low-level plumbing, and since I cannot do it reliably and idiomatically, I figured it was not worth doing at all). So I ended up creating this SSHTunnel written in Go, where library support for this particular task is easily available, and my solution for Cloud9 described in OP can be found here .

+2
source

All Articles