在之前的文章中,我們總結了”ssh代理轉發”的相關知識點,”代理轉發”是針對ssh認證過程的一種轉發,而在這片文章中,我們將會總結ssh中的另外一種轉發:”端口轉發”
“ssh端口轉發”還有一個更加形象的名字,叫做”ssh隧道”,當然,只是純粹的通過”ssh隧道”這幾個字去理解它可能不太容易,我們來描述一些實際的場景,在這些場景中我們可能會遇到一些問題,而這些問題可以通過”ssh隧道”解決,通過這樣的方式,我們反而更加容易理解”ssh隧道”是什么以及它的作用。
假如我們現在有兩個台主機,主機A與主機B,主機A上安裝有mysql客戶端,主機B上安裝有mysql服務端,現在,主機A中的mysql客戶端需要與主機B中的mysql服務端進行通訊,則需要從mysql的客戶端連接到mysql服務端。如下圖所示

然而我們知道,mysql在傳輸數據時是進行明文傳輸的,如果主機A與主機B只能通過公網進行通訊,那么暴露在公網的mysql通訊是非常不安全的,所以,我們需要借助一些手段,提高訪問mysql服務時的安全性,比如,我們可以使用SSL證書為數據加密,或者使用stunnel加密隧道,我們還可以使用VPN,當然,這些方法都不是這篇文章所要描述的重點,我們此處要總結的是”ssh隧道”這種方法,我們可以利用ssh,搭建出一條”通道”,然后將mysq的客戶端與服務端通過這條”ssh通道”連接起來,如下圖所示

mysql的客戶端與服務端的連接方式從原來直連的方式變成了如上圖所示的連接方式,它們之間並不直接進行通訊,而是借助ssh隧道將通訊數據轉發,雖然仍然跨越了公網,但是由於ssh本身的安全特性,所以別人無法看到明文傳輸的數據,數據依靠ssh隧道實現了加密的效果,達到了保護數據安全的作用,提升了mysql的客戶端與服務端通訊的安全性。
本地轉發
經過上述描述,我想你對”ssh隧道”應該已經有了初步的理解,那么現在我們來實際動手配置一下。
首選,將實驗環境准備好,兩台主機的信息如下
ServerA:10.1.0.1
ServerB:10.1.0.2
ServerA中並不存在mysql服務。
ServerB中已經安裝了mysql服務,mysql服務已經啟動並監聽了3306端口。
現在,我們只要在ServerA中執行如下命令,即可在ServerA與ServerB之間建立一條ssh隧道,執行如下命令時會提示輸入ServerB的密碼

如上圖所示,執行上圖中的命令后,我們直接從主機A連接到了主機B,這條連接就是我們創建的”ssh隧道”。
我們先來簡單的解釋一下上圖中命令的含義,為了方便解釋,我們把命令分成3部分理解,如下圖所示。

第1部分為-L選項,-L 選項表示使用”本地轉發”建立ssh隧道,本地轉發是什么意思呢?
“本地轉發”表示本地的某個端口上的通訊數據會被轉發到目標主機的對應端口,你可以把它抽象的理解成一種”映射”,注意,我們把執行上述命令的主機稱為”本地主機”。
比如,訪問本地(當前主機)的端口A,就相當於訪問目標主機的端口B,因為當你訪問本地的端口A時,通訊數據會被轉發到目標主機的端口B,這就是本地轉發,其實,”本地轉發”是與”遠程轉發”相對應的,但是我們還沒有介紹到遠程轉發,所以並不用在意那么多,我們只要先了解本地轉發的作用就行了。
剛才說過,”本地轉發”表示本地的某個端口上的通訊數據會被轉發到目標主機的對應端口,那么你一定能夠理解上述命令中第2部分的含義了
第2部分表示:通訊數據會從本地的9906端口上被轉發,最終被轉發到10.1.0.2的3306端口。
第3部分表示:我們創建的ssh隧道是連接到10.1.0.2上的root用戶的,其實,第3部分可以與之前的ssh連在一起去理解,比如,ssh root@10.1.0.2,其實就是使用ssh命令從ServerA中連接到ServerB的root用戶,這就是為什么執行上述命令以后,會提示我們輸入10.1.0.2中root用戶的密碼,當然,如果你已經在ServerB中配置好了ServerA對應用戶的公鑰,那么則可以省去輸入密碼的步驟直接連接,此時,ServerA的角色是ssh的客戶端,ServerB的角色是ssh的服務端,而這條ssh隧道就是建立在ServerA與ServerB之間的。
了解完上述命令的3個部分,我們來把它當做一個整體去理解一下
ssh -L 9906:10.1.0.2:3306 root@10.1.0.2
上述命令表示從本機(ServerA)建立一個到ServerB(10.1.0.2)的ssh隧道,使用本地端口轉發模式,監聽ServerA本地的9906端口,訪問本機的9906端口時,通訊數據將會被轉發到ServerB(10.1.0.2)的3306端口。
好了,命令解釋完了,現在我們來試試實際的使用效果,注意,此刻我們已經創建了ssh隧道,從serverA中已經連接到了ServerB,不要退出這個ssh連接,否則剛才創建的ssh隧道將會消失(稍后會介紹怎樣后台建立連接),此刻,我們再打開一個新的ssh連接,連接到ServerA,如下圖所示

在新鏈接中查看對應的端口號,本地回環地址的9906端口已經被監聽了(稍后介紹怎樣監聽ServerA中指定的IP,即非本地回環地址)。
此時,我們直接在ServerA中通過mysql命令訪問127.0.0.1的9906端口,就相當於訪問ServerB的mysql服務了,我們來試試。
執行mysql命令時需要指定IP與端口號,因為我的ServerB中的mysql只是用於測試,所以沒有為用戶設置密碼,如下圖即可連接

如上圖所示,已經可以正常在ServerA中連接到數據庫,但是連接的數據庫其實是ServerB中的mysql服務。
這就是通過ssh隧道訪問遠程主機的mysql服務的示例,這樣做就是利用ssh的安全特性加密了mysql的通訊數據。
在沒有使用ssh隧道時,直接從ServerA跨越公網訪問ServerB的mysql服務時,如果在ServerB中通過抓包工具對通訊網卡進行抓包,可以直接從抓到的數據包中看到mysql的傳輸數據。
但是如果使用了ssh隧道,並且在ServerB中僅對通訊網卡進行抓包時,則只能看到經過加密的ssh數據包,此時,如果對ServerB的本地回環網卡同時進行抓包,則可以看到未加密的mysql傳輸數據,不過,這並不影響mysql通訊數據跨越公網時的安全性,因為這時已經是ServerB本機中的數據傳輸了,也就是說,mysql通訊數據在跨越公網時,是經過ssh隧道加密的,mysql通訊數據到達ServerB本機以后,是明文傳輸的。
不過,當我們執行上述命令創建ssh隧道時,總會從ServerA中連接到ServerB中,而通常,我們只希望建立ssh隧道,並不會使用到這個新建立的ssh連接,而且在實際使用中,我們往往會在建立隧道以后,退出當前的ssh會話,所以,上述命令並不能滿足我們的需求,因為,我們一旦退出對應的ssh會話,相應的ssh隧道也會消失,所以,我們還需要配合另外兩個選項,”-N選項”與”-f選項”,我們一一道來。
首先來試試”-N選項”,當配合此選項創建ssh隧道時,並不會打開遠程shell連接到目標主機,我們來試試,如下圖所示,配合-N選項創建隧道,輸入ServerB的密碼以后,並沒有連接到ServerB,而是停留在了如下圖的位置

此時,再打開一個新的ssh會話連接到ServerA,可以看到,9906端口已經被監聽。
但是,這樣仍然不能滿足我們的要求,雖然建立隧道時並沒有連接到ServerB,但是,我們仍然不能關閉創建ssh隧道時所使用的ssh會話。
這時,只要配合”-f”選項即可,”-f”選項表示后台運行ssh隧道,即使我們關閉了創建隧道時所使用的ssh會話,對應的ssh隧道也不會消失,”-f”選項需要跟”-N”選項配合使用,所以通常,我們會使用如下命令創建ssh隧道
ssh -f -N -L 9906:10.1.0.2:3306 root@10.1.0.2
配合上述選項創建ssh隧道時,即使我們完全關閉了執行命令時的ssh會話,對應創建的隧道也可以完全正常運行。
不過,當我們使用上述命令建立隧道時,只有127.0.0.1這個回環地址的9906端口會被監聽,這樣就會出現一個小問題,也就是說,我們只能在ServerA本機上訪問9906端口,並不能通過其他主機訪問ServerA的9906端口,因為ServerA其他IP的9906端口並未被監聽,那么怎么辦呢?很簡單,使用如下命令,即可讓9906端口監聽在ServerA中指定的IP上
ssh -f -N -L 10.1.0.1:9906:10.1.0.2:3306 root@10.1.0.2
在ServerA中執行上述命令時,ServerA的10.1.0.1的9906端口會被監聽,此刻,我們可以通過其他主機訪問10.1.0.1的9906端口,即可訪問到ServerB中的mysql服務,其實,與之前的命令相比,只是在9906前增加了ServerA中對應的IP地址罷了,很簡單吧。
如果你覺得這還不夠,希望ServerA中的所有IP地址的9906端口都被監聽,那么可以在建立隧道時開啟”網關功能”,使用”-g”選項可以開啟”網關功能”,開啟網關功能以后,ServerA中的所有IP都會監聽對應端口,示例如下

好了,說了這么多,終於把ssh隧道(本地轉發)給解釋明白了,不過,我們也只是說明了本地轉發,現在,我們來聊聊遠程轉發。
遠程轉發
在了解遠程轉發之前,請先確定你已經理解了”本地轉發”。
老規矩,為了方便理解,我們先來描述一個場景。
公司有一台服務器ServerB,ServerB處於公司的內網中,公司內網中的所有主機都通過路由器訪問互聯網(典型的NAT網絡),ServerB中有提供mysql服務,如果此時,我們想要通過外網訪問到ServerB中的mysql服務,該怎么辦呢?通常的做法是,通過路由器或者防火牆,將公司的固定外網IP上的某個端口映射到ServerB內網IP的3306端口上,這樣,我們只要訪問公司外網IP的對應端口,即可訪問到內網ServerB中的mysql服務了,但是,如果你沒有權限控制公司的防火牆或者路由器呢,這時該怎么辦呢?
假設,你無法控制防火牆去進行端口映射,但是,公司在公網上有另外一台服務器ServerA,ServerA有自己的公網IP,你有權控制ServerA,這時,我們就可以利用ServerA達到我們的目的,聰明如你,一定想到了解決方案,沒錯,我們可以在ServerA與ServerB之間創建一條SSH隧道,利用這條隧道將ServerA中的某個端口(假設仍然使用9906端口)與ServerB中的3306端口連接起來,這樣,當我們訪問ServerA的9906端口時,就相當於訪問到內網ServerB中的mysql服務了,那么,我們能不能使用之前的”本地轉發”的方式,在ServerA中創建SSH隧道呢?我們來模擬一下,看看會不會遇到什么問題,如果想要使用之前的命令創建SSH隧道,那么我們則需要在ServerA中執行如下命令。
ssh -f -N -L AIP:9906:BIP:3306 root@BIP
問題來了,ServerA有自己的公網IP,我們只要把上述命令中的AIP替換成ServerA的公網IP即可,但是ServerB是內網主機,雖然ServerB能夠通過公司內的路由器訪問到互聯網,但是ServerB並不持有任何公網IP,ServerB只有內網IP,所以,我們並不可能把上述命令中的BIP替換成B主機的內網IP,所以,使用上述命令是無法在ServerA中創建ssh隧道連接到ServerB的,那么該怎么辦呢?
雖然我們無法從ServerA中使用ssh命令連接到ServerB,但是,我們可以從ServerB中使用ssh命令連接到ServerA啊,雖然ServerB是沒有公網IP的內網主機,但是它仍然可以依靠公司的路由器訪問互聯網,所以,我們只要在ServerB中執行如下命令,即可從ServerB中連接到ServerA中。
ssh root@AIP
那么,按照這個思路,我們似乎找到了方向,我們現在需要一種方法,能夠從ServerB中創建SSH隧道連接到ServerA,並且,隧道創建后,ServerA中會監聽9906端口,以便別人能夠通過外網訪問,也就是說,我們需要一種方法,能夠滿足如下兩個條件
條件1:從ServerB中主動連接到ServerA,即在ServerB中執行創建隧道的命令,連接到ServerA。
條件2:隧道創建后,轉發端口需要監聽在ServerA中,以便利用ServerA訪問到內網的ServerB。
這種方法就是”遠程轉發”。
你可能還是不太明白,沒有關系,我們先來實際動手操作一下,稍后,我們會對比本地轉發與遠程轉發的具體區別。
為了方便,我們仍然使用之前的實驗環境,假設ServerA是外網主機,ServerB是內網主機,ServerA的IP為10.1.0.1(假設此IP為公網IP),ServerB的IP為10.1.0.2,並且已經將之前本地轉發的進程關閉,相當於一個沒有任何隧道的新的實驗環境。
使用”-R選項”,可以創建一個”遠程轉發”模式的ssh隧道,我們在ServerB中,執行如下命令即可

上述命令在ServerB中執行,執行后,即可在ServerA與ServerB之間建立ssh隧道,此時,ServerB是ssh客戶端,ServerA是ssh服務端,隧道建立后,ServerA中的9906端口會被監聽,在ServerA中查看對應端口,如下圖所示

從圖中可以看出,ServerA中的9906端口已經被監聽,此刻,我們通過外網IP登錄到ServerA,在ServerA中訪問本地回環地址的9906端口,即可訪問到內網ServerB中的mysql服務,如下圖所示。

不過你肯定注意到了,當使用遠程轉發的命令時,我並沒有指定監聽ServerA的外網IP,也沒有使用”-g選項”開啟網關功能,這是因為,即使你在命令中指定了IP地址,最終在ServerA中還是會只監聽127.0.0.1的9906端口,你可以在ServerB中嘗試一下如下命令
ssh -f -N -R 10.1.0.1:9906:10.1.0.2:3306 root@10.1.0.1
即使在ServerB中執行上述命令時指定了IP或者開啟了網關功能,ServerA的9906端口仍然只監聽在127.0.0.1上,當然,如果你一心想要通過別的主機訪問ServerA的9906端口,也可以使用其他程序去反代ServerA的9906端口,還有,我在實際的使用過程中,如果使用遠程轉發穿透到內網,ssh隧道將會非常不穩定,隧道會莫名其妙的消失或者失效,特別是在沒有固定IP的網絡內,網上有些朋友提供了autossh的解決方案,不過我並沒有嘗試過,如果你有興趣,可以試一試。
本地轉發與遠程轉發的區別
讀到此處,你可能會有些蒙圈,”遠程轉發”與”本地轉發”到底有什么不一樣,我們來對比一下
在對比之前,再強調一點,我們把執行創建隧道命令的主機稱為本地主機(本地)。
“本地轉發”
在本機執行創建隧道的命令時,本地是ssh客戶端,隧道的另一頭是遠程主機(ssh服務端),本地主機(也就是ssh客戶端)會監聽一個端口,當訪問本地主機的這個端口時,通訊數據會通過ssh隧道轉發到ssh服務端(即遠程主機),遠程主機再將通訊數據發往應用服務所監聽端口,在本地轉發中,本地主機不僅扮演了ssh客戶端的角色,也扮演了應用程序的客戶端(比如mysql客戶端),遠程主機不僅扮演了ssh服務端,也扮演了應用程序服務端(比如mysql服務端),那么我們可以總結一下,本地轉發的特性如下
本地主機:隧道的一頭,本地主機既是ssh客戶端,又是應用客戶端
遠程主機:隧道的另一頭,遠程主機既是ssh服務端,又是應用服務端
隧道創建以后,轉發端口監聽在本地主機中,即監聽在ssh客戶端主機中。
“遠程轉發”
在本機執行創建隧道的命令時,本地是ssh客戶端,隧道的另一頭是遠程主機(ssh服務端),遠程主機(也就是ssh服務端)會監聽一個端口,當訪問遠程主機的這個端口時,通訊數據會通過ssh隧道轉發到ssh客戶端(即本地主機),本地主機再將通訊數據發往應用服務所監聽端口,在遠程轉發中,本地主機不僅扮演了ssh客戶端的角色,也扮演了應用程序的服務端(比如mysql服務端),遠程主機不僅扮演了ssh服務端,也扮演了應用程序客戶端(比如mysql客戶端),那么我們可以總結一下,遠程轉發的特性如下
本地主機:隧道的一頭,本地主機既是ssh客戶端,又是應用服務端
遠程主機:隧道的另一頭,遠程主機既是ssh服務端,又是應用客戶端
隧道創建以后,轉發端口監聽在遠程主機中,即監聽在ssh服務端主機中。
“本地轉發”與”遠程轉發”都屬於ssh端口轉發,也可以稱呼它們為”ssh隧道”,只不過,有的朋友喜歡將”遠程轉發”稱呼為為”ssh反向隧道”或者”ssh逆向隧道”
經過上述描述,我想你應該已經明白了它們之間的區別。
一些擴展
在之前的示例中,ServerB是ssh隧道的一頭,同時,ServerB也是應用的服務端,也就是說,應用程序的服務端與ssh隧道的連接端在同一台服務器上,那么,當應用程序的服務端處於其他主機時(比如ServerC),我們還能夠通過ServerB去轉發通訊數據嗎?我們來動手試試,不過在動手之前,先來描述一下實驗場景,實驗場景如下圖所示

如上圖所示,我們想要在A與B之間創建隧道,最終通過隧道訪問到ServerC中的mysql服務。
ServerAIP:10.1.0.1
ServerBIP:10.1.0.2
ServerCIP:10.1.0.3
ServerA與ServerB上沒有開啟任何mysql服務。
ServerC中開啟了mysql服務,監聽了3306端口。
之前用於示例所創建的ssh隧道已經全部關閉,相當於一個全新的實驗環境。
好了,實驗環境描述完畢,現在開始實際操作,就以本地轉發為例,在ServerA中執行如下命令,即可創建一條隧道並滿足上圖中的應用場景。

如上圖所示,ServerA的9906端口已經被監聽,細心如你一定發現了,上圖中的命令與之前創建隧道時所使用的命令在結構上並沒有什么不同,只是目標端口所對應的IP地址變為了ServerC的IP,是不是很簡單,我再來啰嗦一遍,上述命令表示,從本機(ServerA)建立一條ssh隧道連接到10.1.0.2(ServerB),隧道使用本地轉發模式建立,轉發端口監聽在本地的9906端口上,訪問本機的9906端口時,數據會被ssh隧道轉發到10.1.0.3(ServerC)的3306端口。
我們來測試一下實際的使用效果,如下圖所示,一切正常。

上述場景中存在一個問題,就是數據安全性的問題,我們之所以使用ssh隧道,就是為了用它來保護明文傳輸的數據,從而提升安全性,不過,在上例的場景中,只有ServerA與ServerB之間的傳輸是受ssh隧道保護的,ServerB與ServerC之間的傳輸,仍然是明文的,所以,如果想要在上述場景中使用ssh隧道進行數據轉發,首先要考慮ServerB與ServerC之間的網絡是否可靠。
其實,當我們在創建隧道時如果開啟了網關功能,那么應用客戶端與ServerA之間的通訊也會面臨同樣的問題,如下圖所示

既然上述場景中存在沒有辦法通過ssh隧道保護的連接,那么為什么還要使用上述方式進行轉發呢?
這是因為,在某些實際的使用場景中,我們使用ssh隧道的目的並不是提升數據的安全性,而是為了”繞過防火牆”,比如如下場景

上圖中,ServerC中提供了mysql服務,我們想要通過ServerA訪問ServerC中的mysql服務,但是,ServerA與ServerC之間存在防火牆,阻斷了它們的通訊,所以,我們無法從ServerA中直接訪問ServerC中的服務,不過幸運的是,我們還有另外一台機器:ServerB,ServerA與ServerB之間可以自由通訊,同時,ServerB與ServerC之間也可以自由通訊,沒錯,你一定想到了,我們可以利用ServerB,在ServerA與ServerB之間建立ssh隧道,達到我們的最終目的:使得ServerA可以訪問到ServerC中的mysql服務,如下圖所示

當上圖中的ssh隧道建立以后,訪問ServerA中的轉發端口,即可訪問到ServerC中的mysql服務,因為對於ServerC來說,ServerA是透明的,ServerC並不知道ServerA的存在,它只能看到ServerB,當你在ServerA中使用ssh隧道訪問ServerC的mysql服務時,如果你在ServerC中的網卡上進行抓包,只會看到ServerB的IP地址,因為數據經過ServerB轉發了。
一些配置
其實,如果想要能夠正常的使用ssh端口轉發,我們還需要做出正確的配置才行,之前一直沒有說明,是因為openssh默認的配置就是支持端口轉發的。
如果想要ssh端口轉發能夠正常工作,需要在ssh服務端的配置文件中將AllowTcpForwarding的值設置為yes。
此處所指的ssh服務端即ssh隧道中的一頭,扮演ssh服務端角色的那台主機。
當隧道建立以后,經過一段時間后,ssh隧道鏈接可能會被斷開,這有可能是因為ssh客戶端和ssh服務端長時間沒有通訊,於是ssh服務端主動斷開了鏈接,如果想要解決這個問題,可以在ssh服務端進行配置,調整ssh服務端的ClientAliveInterval配置和ClientAliveCountMax配置即可。
小結
經過上述描述,我想你應該已經了解的ssh隧道的作用。
通常,ssh隧道可以幫助我們達到如下目的:
1、保護tcp會話,保護會話中明文傳輸的內容。
2、繞過防火牆或者穿透到內網,訪問對應的服務。
為了以后方便回顧,我們將上文中使用到的命令及選項進行總結
創建隧道時的常用選項有:
“-L選項”:表示使用本地端口轉發創建ssh隧道
“-R選項”:表示使用遠程端口轉發創建ssh隧道
“-N選項”: 表示創建隧道以后不連接到sshServer端,通常與”-f”選項連用
“-f選項”:表示在后台運行ssh隧道,通常與”-N”選項連用
“-g選項”:表示ssh隧道對應的轉發端口將監聽在主機的所有IP中,不使用”-g選項”時,轉發端口默認只監聽在主機的本地回環地址中,”-g”表示開啟網關模式,遠程端口轉發中,無法開啟網關功能。
創建本地轉發模式的ssh隧道,命令如下
本機上的forwardingPort將會被監聽,訪問本機的forwardingPort,就相當於訪問targetIP的targetPort,ssh隧道建立在本機與sshServer之間。
創建遠程轉發模式的ssh隧道,命令如下
sshServer上的forwardingPort將會被監聽,訪問sshServer上的forwardingPort,就相當於訪問targetIP的targetPort,ssh隧道建立在本機與sshServer之間。
關於ssh的端口轉發就總結到這里,希望可以幫助到你。
