SSH 隧道與轉發內網穿透(轉載)
大家都知道 SSH 是一種安全的傳輸協議,用在連線伺服器上比較多。不過其實除了這個功能,它的隧道轉發功能更是吸引人。下面是個人根據自己的需求以及在網上查詢的資料配合自己的實際操作所得到的一些心得。
SSH/plink 命令的基本資料
$ ssh -C -f -N -g -L listen_port:DST_Host:DST_port user@Tunnel_Host $ ssh -C -f -N -g -R listen_port:DST_Host:DST_port user@Tunnel_Host $ ssh -C -f -N -g -D listen_port user@Tunnel_Host
相關引數的解釋:
-f
Fork into background after authentication.
後臺認證使用者/密碼,通常和 -N
連用,不用登入到遠端主機。
-L
port:host:hostport
將本地機(客戶機)的某個埠轉發到遠端指定機器的指定埠. 工作原理是這樣的, 本地機器上分配了一個 socket 偵聽 port 埠, 一旦這個埠上有了連線, 該連線就經過安全通道轉發出去, 同時遠端主機和 host 的 hostport 埠建立連線. 可以在配置檔案中指定埠的轉發. 只有 root 才能轉發特權埠. IPv6 地址用另一種格式說明: port/host/hostport
-R
port:host:hostport
將遠端主機(伺服器)的某個埠轉發到本地端指定機器的指定埠. 工作原理是這樣的, 遠端主機上分配了一個 socket 偵聽 port 埠, 一旦這個埠上有了連線, 該連線就經過安全通道轉向出去, 同時本地主機和 host 的 hostport 埠建立連線. 可以在配置檔案中指定埠的轉發. 只有用 root 登入遠端主機才能轉發特權埠. IPv6 地址用另一種格式說明: port/host/hostport
-D
port
指定一個本地機器「動態的」應用程式埠轉發. 工作原理是這樣的, 本地機器上分配了一個 socket 偵聽 port 埠, 一旦這個埠上有了連線, 該連線就經過安全通道轉發出去, 根據應用程式的協議可以判斷出遠端主機將和哪裡連線. 目前支援 SOCKS4 協議, 將充當 SOCKS4 伺服器. 只有 root 才能轉發特權埠. 可以在配置檔案中指定動態埠的轉發.
-C
Enable compression.
壓縮資料傳輸。
-N
Do not execute a shell or command.
不執行指令碼或命令,通常與 -f
連用。
-g
Allow remote hosts to connect to forwarded ports.
在 -L
/-R
/-D
引數中,允許遠端主機連線到建立的轉發的埠,如果不加這個引數,只允許本地主機建立連線。
注:這個引數我在實踐中似乎始終不起作用。
建立本地 SSH 隧道例子
在我們計劃建立一個本地 SSH 隧道之前,我們必須清楚下面這些資料:
中間伺服器 d 的 IP 地址 要訪問伺服器 c 的 IP 地址和埠
現在,我們把上面這張圖變得具體一些,給這些機器加上 IP 地址。並且根據下面這張圖列出我們的計劃:
需要訪問 234.234.234.234 的 FTP 服務,也就是埠 21
,中間伺服器是 123.123.123.123
現在我們使用下面這條命令來達成我們的目的
$ ssh -N -f -L 2121:234.234.234.234:21 123.123.123.123
$ ftp localhost:2121
# 現在訪問本地2121埠,就能連線234.234.234.234的21埠了
這裡我們用到了 SSH 客戶端的三個引數,下面我們一一做出解釋:
-N
告訴SSH客戶端,這個連線不需要執行任何命令。僅僅做埠轉發
-f
告訴SSH客戶端在後臺執行
L
做本地對映埠,被冒號分割的三個部分含義分別是
需要使用的本地埠號
需要訪問的目標機器IP地址(IP: 234.234.234.234)
需要訪問的目標機器埠(埠: 21)
最後一個引數是我們用來建立隧道的中間機器的IP地址(IP: 123.123.123.123)
我們再重複一下 -L
引數的行為。-L X:Y:Z 的含義是,將 IP 為 Y 的機器的 Z 埠通過中間伺服器對映到本地機器的 X 埠。
在這條命令成功執行之後,我們已經具有繞過公司防火牆的能力,並且成功訪問到了我們喜歡的一個 FTP 伺服器了。
如何建立遠端 SSH 隧道
通過建立本地 SSH 隧道,我們成功地繞過防火牆開始下載 FTP 上的資源了。那麼當我們在家裡的時候想要察看下載進度怎麼辦呢?大多數公司的網路是通過路由器接入網際網路的,公司內部的機器不會直接與網際網路連線,也就是不能通過網際網路直接訪問。通過線路 D-B-A 訪問公司裡的機器 a 便是不可能的。也許你已經注意到了,雖然 D-B-A 這個方向的連線不通,但是 A-B-D 這個方向的連線是沒有問題的。那麼,我們能否利用一條已經連線好的 A-B-D 方向的連線來完成 D-B-A 方向的訪問呢?答案是肯定的,這就是遠端 SSH 隧道的用途。
與本地 SSH 一樣,我們在建立遠端 SSH 隧道之前要清楚下面幾個引數:
需要訪問內部機器的遠端機器的IP地址(這裡是123.123.123.123)
需要讓遠端機器能訪問的內部機器的IP地址(這裡因為是想把本機映射出去,因此IP是127.0.0.1)
需要讓遠端機器能訪問的內部機器的埠號(埠:22)
在清楚了上面的引數後,我們使用下面的命令來建立一個遠端SSH隧道
$ ssh -N -f -R 2222:127.0.0.1:22 123.123.123.123
現在,在 IP 是 123.123.123.123 的機器上我們用下面的命令就可以登陸公司的 IP 是 192.168.0.100 的機器了。
$ ssh -p 2222 localhost
-N
-f
這兩個引數我們已經在本地 SSH 隧道中介紹過了。我們現在重點說說引數 -R
。該引數的三個部分的含義分別是:
遠端機器使用的埠(2222)
需要對映的內部機器的IP地址(127.0.0.1)
需要對映的內部機器的埠(22)
例如:-R X:Y:Z 就是把我們內部的Y機器的Z埠對映到遠端機器的X埠上。
建立 SSH 隧道的幾個技巧
自動重連
隧道可能因為某些原因斷開,例如:機器重啟,長時間沒有資料通訊而被路由器切斷等等。因此我們可以用程式控制隧道的重新連線,例如一個簡單的迴圈或者使用 djb’s daemontools . 不管用哪種方法,重連時都應避免因輸入密碼而卡死程式。關於如何安全的避免輸入密碼的方法,請參考我的 如何實現安全的免密碼ssh登入 。這裡請注意,如果通過其他程式控制隧道連線,應當避免將SSH客戶端放到後臺執行,也就是去掉-f引數。
保持長時間連線
有些路由器會把長時間沒有通訊的連線斷開。SSH 客戶端的 TCPKeepAlive 選項可以避免這個問題的發生,預設情況下它是被開啟的。如果它被關閉了,可以在 ssh 的命令上加上 -o
TCPKeepAlive=yes 來開啟。
另一種方法是,去掉 -N
引數,加入一個定期能產生輸出的命令。例如: top 或者 vmstat。下面給出一個這種方法的例子:
$ ssh -R 2222:localhost:22 123.123.123.123 "vmstat 30"
檢查隧道狀態
有些時候隧道會因為一些原因通訊不暢而卡死,例如:由於傳輸資料量太大,被路由器帶入 stalled 狀態。這種時候,往往 SSH 客戶端並不退出,而是卡死在那裡。一種應對方法是,使用 SSH 客戶端的 ServerAliveInterval
和 ServerAliveCountMax
選項。 ServerAliveInterval
會在隧道無通訊後的一段設定好的時間後傳送一個請求給伺服器要求伺服器響應。如果伺服器在 ServerAliveCountMax
次請求後都沒能響應,那麼 SSH 客戶端就自動斷開連線並退出,將控制權交給你的監控程式。這兩個選項的設定方法分別是在 ssh 時加入 -o ServerAliveInterval=n
和 -o ServerAliveCountMax=m
。
如何將埠繫結到外部地址上
使用上面的方法,對映的埠只能繫結在 127.0.0.1 這個介面上。也就是說,只能被本機自己訪問到。如何才能讓其他機器訪問這個埠呢?我們可以把這個對映的埠繫結在 0.0.0.0 的介面上,方法是加上引數 -b 0.0.0.0。同時還需要開啟 SSH 伺服器端的一個選項 -GatewayPorts。預設情況下它應當是被開啟的。如果被關閉的話,可以在 /etc/sshd_config
中修改 GatewayPorts no
為 GatewayPorts yes
來開啟它。
通過 SSH 隧道建立 SOCKS 伺服器
如果我們需要藉助一臺中間伺服器訪問很多資源,一個個對映顯然不是高明的辦法(事實上,高明確實沒有用這個方法)。幸好,SSH 客戶端為我們提供了通過SSH隧道建立 SOCKS 伺服器的功能。
通過下面的命令我們可以建立一個通過 123.123.123.123 的 SOCKS 伺服器。
$ ssh -N -f -D 1080 123.123.123
# 將埠繫結在127.0.0.1上
$ ssh -N -f -D 0.0.0.0:1080 123.123.123.123
# 將埠繫結在0.0.0.0上
通過 SSH 建立的 SOCKS 伺服器使用的是 SOCKS5 協議,在為應用程式設定 SOCKS 代理的時候要特別注意。