在Rust中通过SSH的TCP隧道

dlu*_*kes 6 ssh tcp http tunnel rust

我正在尝试在Rust中编写一个小程序来完成基本的ssh -L 5000:localhost:8080工作:localhost:5000在我的机器和localhost:8080远程机器之间建立隧道,这样如果HTTP服务器在远程端口8080上运行,我可以访问它我的本地通道localhost:5000,绕过远程的防火墙,可能会阻止外部访问8080.

ssh已经意识到已经完成了这个并且可靠,这是一个学习项目,如果我让它工作,我可能会添加一些功能:)这是一个准系统(没有线程,没有错误处理)版本的我已经出现到目前为止(应该在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'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(&mut response).unwrap();
    stream.write(&response).unwrap();
    println!("SENT {} BYTES AS RESPONSE", read_bytes);
  };
}
Run Code Online (Sandbox Code Playgroud)

事实证明,这种作品,但并不完全.例如,如果在远程服务器上运行的应用程序是Cloud9 IDE Core/SDK,则会加载主HTML页面和一些资源,但是对其他资源(.js,.css)的请求会系统地返回空(无论是主页还是直接请求) ),即在调用中没有读取任何内容channel.read_to_end().其他(更简单?)网络应用程序或静态网站似乎工作正常.至关重要的是,使用时ssh -L 5000:localhost:8080,即使是Cloud9 Core也能正常工作.

我预计其他更复杂的应用也会受到影响.我看到我的代码中潜在的bug可能存在的各种潜在区域:

  1. Rust的流读/写API:也许调用的channel.read_to_end()工作方式与我想的不同,只是偶然为某些请求做了正确的事情?
  2. HTTP:也许我需要在将请求转发到远程服务器之前修改HTTP头?或者我可能只是打电话给我很快就放弃了响应流channel.read_to_end()
  3. Rust本身 - 这是我第一次相对认真地学习系统编程语言

我已经尝试过使用上面的一些内容,但我会感谢任何探索路径的建议,最好还有一个解释,为什么这可能是问题:)

dlu*_*kes -1

tl;dr:使用 Go 及其网络库来完成此特定任务

事实证明,我对 HTTP 工作原理的非常基本的理解可能有问题(我最初以为我可以通过 ssh 连接来回传输数据,但我还没有能够实现这一点——如果有人知道一种方法的话)这样做,我仍然很好奇!)。请参阅评论中的一些建议,但基本上可以归结为 HTTP 连接如何启动、保持活动状态和关闭的复杂性。我尝试使用hyper crate 来抽象这些细节,但由于线程安全限制,ssh2 Session 不能在 hyper handlers 中使用(至少在 2016 年不能)。

在这一点上,我认为初学者没有简单高级的方法可以在 Rust 中实现这一目标(我必须先做一些低级的管道,并且由于我无法可靠且惯用地做到这一点,所以我想这根本不值得做)。因此,我最终分叉了这个用 Go 编写的SSHTunnel存储库,其中可以轻松获得对此特定任务的库支持,并且可以在此处找到 OP 中描述的 Cloud9 设置的解决方案。