TCP tunnel over SSH in Rust
<p>I'm trying to write a small program in Rust to accomplish basically what <code>ssh -L 5000:localhost:8080</code> does: establish a tunnel between <code>localhost:5000</code> on my machine and <code>localhost:8080</code> on a remote machine, so that if an HTTP server is running on port 8080 on the remote, I can access it on my local via <code>localhost:5000</code>, bypassing the remote's firewall which might be blocking external access to 8080.</p>

<p>I realize <code>ssh</code> already does exactly this and reliably, this is a learning project, plus I might be adding some functionality if I get it to work :) This is a barebones (no threading, no error handling) version of what I've come up with so far (should compile on Rust 1.8):</p>

<pre><code>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("&lt;IPv4&gt;:22").unwrap();
  let mut session = ssh2::Session::new().unwrap();
  session.handshake(&amp;tcp).unwrap();
  // substitute appropriate values for username and password
  // session.userauth_password("&lt;username&gt;", "&lt;password&gt;").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(&amp;mut request).unwrap();
    println!("REQUEST ({} BYTES):\n{}", read_bytes, str::from_utf8(&amp;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(&amp;request).unwrap();

    // read the remote server's response (all of it, for simplicity's sake)
    // and forward it to the local TCP connection's stream
    let mut response = Vec::new();
    let read_bytes = channel.read_to_end(&amp;mut response).unwrap();
    stream.write(&amp;response).unwrap();
    println!("SENT {} BYTES AS RESPONSE", read_bytes);
  };
}
</code></pre>

<p>As it turns out, this kind of works, but not quite. E.g. if the app running on the remote server is the <a href="https://github.com/c9/core" rel="noreferrer">Cloud9 IDE Core/SDK</a>, the main HTML page gets loaded and some resources as well, but <strong>requests for other resources</strong> (<code>.js</code>, <code>.css</code>) <strong>systematically come back empty</strong> (whether requested by the main page or directly), i.e. <strong>nothing is read in the call to <code>channel.read_to_end()</code></strong>. Other (simpler?) web apps or static sites seem to work fine. Crucially, when using <code>ssh -L 5000:localhost:8080</code>, even Cloud9 Core works fine.</p>

<p>I expect that other more complex apps will also be affected. I see various potential areas where the bug might be lurking in my code:</p>

<ol>
<li>Rust's stream reading/writing APIs: maybe the call to <code>channel.read_to_end()</code> works differently than I think and just accidentally does the right thing for some kinds of requests?</li>
<li>HTTP: maybe I need to tinker with HTTP headers before forwarding the request to the remote server? or maybe I'm giving up to soon on the response stream by just calling <code>channel.read_to_end()</code>?</li>
<li>Rust itself -- it's my first relatively earnest attempt at learning a systems programming language</li>
</ol>

<p>I've already tried playing with some of the above, but I'll appreciate any  suggestions of paths to explore, preferably along with an explanation as to why that might be the problem :)</p>