title: TCP tunnel over SSH in Rust
body: <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>