在Rust中通过SSH的TCP隧道 [英] TCP tunnel over SSH in Rust

查看:917
本文介绍了在Rust中通过SSH的TCP隧道的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Rust中编写一个小程序,以基本上完​​成 ssh -L 5000:localhost:8080 :确定 localhost:5000 在我的机器上, localhost:8080 在远程计算机上,这样如果HTTP服务器在远程计算机上的端口8080上运行,我可以通过 localhost:5000 在我的本地访问它,绕过远程的防火墙,这可能会阻止对8080的外部访问。

I'm trying to write a small program in Rust to accomplish basically what ssh -L 5000:localhost:8080 does: establish a tunnel between localhost:5000 on my machine and localhost:8080 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 localhost:5000, bypassing the remote's firewall which might be blocking external access to 8080.

我意识到 ssh 已经完成了这个并且可靠,这是一个学习项目,如果我让它工作,我可能会添加一些功能:)这是到目前为止我提出的一个准系统(没有线程,没有错误处理)版本(应该在Rust 1.8上编译):

I realize ssh 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):

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);
  };
}

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

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

我希望其他更复杂的应用程序也会受到影响。我在我的代码中看到了潜在的潜在区域:

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:


  1. Rust的流读/写API:可能是对<$的调用c $ c> channel.read_to_end()的工作方式与我想的不同,只是偶然为某些类型的请求做了正确的事情?

  2. HTTP:也许我需要在将请求转发到远程服务器之前修改HTTP标头?或者我可以通过调用 channel.read_to_end()来尽快放弃响应流?

  3. Rust本身 - 这是我学习系统编程语言的第一次相对认真的尝试

  1. Rust's stream reading/writing APIs: maybe the call to channel.read_to_end() works differently than I think and just accidentally does the right thing for some kinds of requests?
  2. 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 channel.read_to_end()?
  3. Rust itself -- it's my first relatively earnest attempt at learning a systems programming language

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

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 :)

推荐答案

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

tl;dr: use Go and its networking libraries for this particular task

结果我对HTTP的工作方式的基本理解可能是错误的(我最初认为我可以在ssh连接上来回挖掘数据,但我无法实现这一点 - 如果有人知道这样做的方法,我仍然很好奇!)。请参阅注释中的一些建议,但基本上它归结为HTTP连接如何启动,保持活动和关闭的复杂性。我尝试使用超级包装箱来抽象出这些细节,但事实证明是ssh2箱子(就像底层libssh2)不是线程安全的,这使得无法在超级处理程序中使用ssh Session。

Turns out my very rudimentary understanding of how HTTP works may be at fault here (I initially thought I could just shovel data back and forth over the ssh connection, but I haven't been able to achieve that -- if someone knows of a way to do this, I'm still curious!). See some of the suggestions in the comments, but basically it boils down to the intricacies of how HTTP connections are initiated, kept alive and closed. I tried using the hyper crate to abstract away these details, but it turns out that the ssh2 crate (like the underlying libssh2) is not threadsafe, which makes it impossible to use the ssh Session in hyper handlers.

此时,我认为没有简单初学方式让初学者在Rust中实现这一点(我必须先做一些低级管道,因为我不能这样做可靠和惯用,我认为它根本不值得做。所以我最终分发了用Go编写的 SSHTunnel 存储库,其中可以随时获得对此特定任务的库支持,我可以在这里找到我在OP中描述的Cloud9设置的解决方案。

At this point, I decided there's no simple, high-level way for a beginner to achieve this in Rust (I'd have to do some low-level plumbing first, and since I can't do that reliably and idiomatically, I figured it's not worth doing at all). So I ended up forking this SSHTunnel repository written in Go, where library support for this particular task is readily available, and my solution to the Cloud9 setup described in the OP can be found here.

这篇关于在Rust中通过SSH的TCP隧道的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆