對(duì)應(yīng)的情況
這篇文章主要介紹了如何利用SSH 反向隧道穿透NAT,并演示了如何維持一條穩(wěn)定的SSH 隧道。
假設(shè)有機(jī)器A 和B,A 有公網(wǎng)IP,B 位于NAT 之后并無(wú)可用的端口轉(zhuǎn)發(fā),現(xiàn)在想由A 主動(dòng)向B 發(fā)起SSH 連接。由于B 在NAT 后端,無(wú)可用公網(wǎng)IP + 端口 這樣一個(gè)組合,所以A 無(wú)法穿透NAT,這篇文章應(yīng)對(duì)的就是這種情況。
首先有如下約定,因?yàn)楹苤匾苑旁谇懊妫?div style="height:15px;">
機(jī)器代號(hào)機(jī)器位置地址賬戶ssh/sshd 端口是否需要運(yùn)行sshd
A位于公網(wǎng)a.siteusera22是
B位于NAT之后127.0.0.1userb22是
C位于NAT之后127.0.0.1userc22否
這里默認(rèn)你的系統(tǒng)init 程序?yàn)閟ystemd,如果你使用其他的init 程序,如果沒(méi)有特殊理由還是換到一個(gè)現(xiàn)代化的GNU/Linux 系統(tǒng)吧……
SSH 反向隧道
這種手段實(shí)質(zhì)上是由B 向A 主動(dòng)地建立一個(gè)SSH 隧道,將A 的6766 端口轉(zhuǎn)發(fā)到B 的22 端口上,只要這條隧道不關(guān)閉,這個(gè)轉(zhuǎn)發(fā)就是有效的。有了這個(gè)端口轉(zhuǎn)發(fā),只需要訪問(wèn)A 的6766 端口反向連接B 即可。
首先在B 上建立一個(gè)SSH 隧道,將A 的6766 端口轉(zhuǎn)發(fā)到B 的22 端口上:
B:
$ ssh -p 22 -qngfNTR 6766:127.0.0.1:22 usera@a.site然后在A 上利用6766 端口反向SSH 到B:
A:
$ ssh -p 6766 userb@127.0.0.1要做的事情其實(shí)就是這么簡(jiǎn)單。
隧道的維持
穩(wěn)定性維持
然而不幸的是SSH 連接是會(huì)超時(shí)關(guān)閉的,如果連接關(guān)閉,隧道無(wú)法維持,那么A 就無(wú)法利用反向隧道穿透B 所在的NAT 了,為此我們需要一種方案來(lái)提供一條穩(wěn)定的SSH 反向隧道。
一個(gè)最簡(jiǎn)單的方法就是autossh,這個(gè)軟件會(huì)在超時(shí)之后自動(dòng)重新建立SSH 隧道,這樣就解決了隧道的穩(wěn)定性問(wèn)題,如果你使用Arch Linux,你可以這樣獲得它:
$ sudo pacman -S autossh下面在B 上做之前類似的事情,不同的是該隧道會(huì)由autossh 來(lái)維持:
B:
$ autossh -p 22 -M 6777 -NR 6766:127.0.0.1:22 usera@a.site-M 參數(shù)指定的端口用來(lái)監(jiān)聽(tīng)隧道的狀態(tài),與端口轉(zhuǎn)發(fā)無(wú)關(guān)。
之后你可以在A 上通過(guò)6766 端口訪問(wèn)B 了:
A:
$ ssh -p 6766 userb@127.0.0.1隧道的自動(dòng)建立
然而這又有了另外一個(gè)問(wèn)題,如果B 重啟隧道就會(huì)消失。那么需要有一種手段在B 每次啟動(dòng)時(shí)使用autossh 來(lái)建立SSH 隧道。很自然的一個(gè)想法就是做成服務(wù),之后會(huì)給出在systemd 下的一種解決方案。
“打洞”
之所以標(biāo)題這么起,是因?yàn)樽约河X(jué)得這件事情有點(diǎn)類似于UDP 打洞,即通過(guò)一臺(tái)在公網(wǎng)的機(jī)器,讓兩臺(tái)分別位于各自NAT 之后的機(jī)器可以建立SSH 連接。
下面演示如何使用SSH 反向隧道,讓C 連接到B。
首先在A 上編輯sshd 的配置文件/etc/ssh/sshd_config,將GatewayPorts 開(kāi)關(guān)打開(kāi):
GatewayPorts yes然后重啟sshd:
A:
$ sudo systemctl restart sshd然后在B 上對(duì)之前用到的autossh 指令略加修改:
B:
$ autossh -p 22 -M 6777 -NR '*:6766:127.0.0.1:22' usera@a.site之后在C 上利用A 的6766 端口SSH 連接到B:
C:
$ ssh -p 6766 userb@a.site至此你已經(jīng)輕而易舉的穿透了兩層NAT。
最終的解決方案
整合一下前面提到的,最終的解決方案如下:
首先打開(kāi)A 上sshd 的GatewayPorts 開(kāi)關(guān),并重啟sshd(如有需要)。
然后在B 上新建一個(gè)用戶autossh,根據(jù)權(quán)限最小化思想,B 上的autossh 服務(wù)將以autossh 用戶的身份運(yùn)行,以盡大可能避免出現(xiàn)安全問(wèn)題:
B:
$ sudo useradd -m autosshB $ sudo passwd autossh緊接著在B 上為autossh 用戶創(chuàng)建SSH 密鑰,并上傳到A:
B:
$ su - autossh$ ssh-keygen -t 'rsa' -C 'autossh@B'$ ssh-copy-id usera@a.site注意該密鑰不要設(shè)置密碼,也就是運(yùn)行ssh-keygen 指令時(shí)盡管一路回車,不要輸入額外的字符。
然后在B 上創(chuàng)建以autossh 用戶權(quán)限調(diào)用autossh 的service 文件。將下面文本寫入到文件/lib/systemd/system/autossh.service,并設(shè)置權(quán)限為644:
[Unit]Description=Auto SSH TunnelAfter=network-online.target[Service]User=autosshType=simpleExecStart=/bin/autossh -p 22 -M 6777 -NR '*:6766:127.0.0.1:22' usera@a.site -i /home/autossh/.ssh/id_rsaExecReload=/bin/kill -HUP $MAINPIDKillMode=processRestart=always[Install]WantedBy=multi-user.targetWantedBy=graphical.target在B 上設(shè)置該服務(wù)自動(dòng)啟動(dòng):
B:
$ sudo systemctl enable autossh如果你愿意,在這之后可以立刻啟動(dòng)它:
B:
$ sudo systemctl start autossh然后你可以在A 上使用這條反向隧道穿透B 所在的NAT SSH 連接到B:
A:
$ ssh -p 6766 userb@127.0.0.1或者是在C 上直接穿透兩層NAT SSH 連接到B:
C:
$ ssh -p 6766 userb@a.site如果你對(duì)SSH 足夠熟悉,你可以利用這條隧道做更多的事情,例如你可以在反向連接時(shí)指定動(dòng)態(tài)端口轉(zhuǎn)發(fā):
C:
$ ssh -p 6766 -qngfNTD 7677 userb@a.site假設(shè)C 是你家中的電腦,A 是你的VPS,B 是你公司的電腦。如果你這樣做了,那么為瀏覽器設(shè)置端口為7677 的sock4 本地(127.0.0.1)代理后,你就可以在家里的瀏覽器上看到公司內(nèi)網(wǎng)的網(wǎng)頁(yè)。