2016-04-26 137 views
6

我想在Rust中編寫一個小程序來基本完成ssh -L 5000:localhost:8080的工作:在我的機器上建立localhost:5000與遠程機器上的localhost:8080之間的隧道,這樣如果HTTP服務器正在運行在遙控器上的端口8080,我可以通過localhost:5000我的地方訪問它,繞過遠程防火牆可能會阻止到8080通過SSH生鏽的TCP隧道

外部訪問我意識到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); 
    }; 
} 

事實證明,這種工作,但不完全。例如。如果遠程服務器上運行的應用程序是Cloud9 IDE Core/SDK,主要的HTML頁面被加載和一些資源爲好,但請求其他資源.js.css系統地回來空(是否請求由主網頁或直接),即在撥打channel.read_to_end()時沒有任何內容。其他(更簡單?)Web應用程序或靜態網站似乎工作正常。最重要的是,當使用ssh -L 5000:localhost:8080時,即使Cloud9 Core也能正常工作。

我預計其他更復雜的應用程序也會受到影響。我看到那裏的錯誤可能潛伏在我的代碼不同的潛在領域:

  1. 鏽病的流讀/寫的API:也許調用channel.read_to_end()作品不同於我認爲,只是偶然做正確的事對於一些類型的請求?
  2. HTTP:在將請求轉發到遠程服務器之前,可能需要修改HTTP標頭?或者我可以通過撥打channel.read_to_end()來放棄響應流?
  3. 鏽本身 - 這是在學習一門系統編程語言

我已經嘗試了一些上述的打我的第一個比較認真的嘗試,但我會感謝任何建議的路徑探索,較好隨着一個解釋,爲什麼這可能是問題:)

+0

謝謝,你是對的,更好地保持焦點:) – dlukes

+0

我無法讓代碼在本地運行,但'vec![0; 8192]'是可疑的。這創建了一個有很多零的向量。如果你從套接字中讀取一個字節,那麼在緩衝區中將會有8191個零。當你寫回來時,你正在編寫所有這些零,這是遠程端必須處理的。你只應該發送你閱讀的數據。 – Shepmaster

+0

此外,您可能需要讀取輸入,直到HTTP標頭完成('\ r \ n \ r \ n',IIRC),然後向上遊發送。 – Shepmaster

回答

2

TL;博士:使用Go和它的網絡庫這個特殊的任務

原來我如何HTTP效果可能是非常基本的瞭解在這裏過錯(我最初以爲我可以通過ssh連接來回傳輸數據,但是我一直無法實現這一點 - 如果有人知道如何做到這一點,我仍然很好奇!)。請參閱評論中的一些建議,但基本上可以歸結爲HTTP連接如何啓動,保持活動和關閉的複雜性。我嘗試使用hyper箱子來抽象出這些細節,但事實證明,ssh2箱子(如底層的libssh2)不是線程安全的,這使得在超級處理程序中無法使用ssh Session。

在這一點上,我決定沒有簡單高層初學者拉斯特來實現這一點(我不得不先做一些低級別的管道的方式,因爲我可以我沒有那麼可靠和慣用,我認爲這根本不值得去做)。所以我最終分配了這個用Go編寫的SSHTunnel存儲庫,其中對這個特定任務的庫支持是隨時可用的,並且我在OP中描述的Cloud9設置的解決方案可以找到here