SS 隧道穿透 內網 NAT
對應的情況
這篇文章主要介紹瞭如何利用SSH 反向隧道穿透NAT,並演示瞭如何維持一條穩定的SSH 隧道。
假設有機器A 和B,A 有公網IP,B 位於NAT 之後並無可用的埠轉發,現在想由A 主動向B 發起SSH 連線。由於B 在NAT 後端,無可用公網IP + 埠 這樣一個組合,所以A 無法穿透NAT,這篇文章應對的就是這種情況。
首先有如下約定,因為很重要所以放在前面:
機器代號 | 機器位置 | 地址 | 賬戶 | ssh/sshd 埠 | 是否需要執行sshd |
---|---|---|---|---|---|
A | 位於公網 | a.site | usera | 22 | 是 |
B | 位於NAT之後 | 127.0.0.1 | userb | 22 | 是 |
C | 位於NAT之後 | 127.0.0.1 | userc | 22 | 否 |
這裡預設你的系統init 程式為systemd,如果你使用其他的init 程式,如果沒有特殊理由還是換到一個現代化的GNU/Linux 系統吧……
SSH 反向隧道
這種手段實質上是由B 向A 主動地建立一個SSH 隧道,將A 的6766 埠轉發到B 的22 埠上,只要這條隧道不關閉,這個轉發就是有效的。有了這個埠轉發,只需要訪問A 的6766 埠反向連線B 即可。
首先在B 上建立一個SSH 隧道,將A 的6766 埠轉發到B 的22 埠上:
B:
$ ssh -p 22 -qngfNTR 6766:127.0.0.1:22 [email protected]
然後在A 上利用6766 埠反向SSH 到B:
A:
$ ssh -p 6766 [email protected]
要做的事情其實就是這麼簡單。
隧道的維持
穩定性維持
然而不幸的是SSH 連線是會超時關閉的,如果連線關閉,隧道無法維持,那麼A 就無法利用反向隧道穿透B 所在的NAT 了,為此我們需要一種方案來提供一條穩定的SSH 反向隧道。
一個最簡單的方法就是autossh,這個軟體會在超時之後自動重新建立SSH 隧道,這樣就解決了隧道的穩定性問題,如果你使用Arch Linux,你可以這樣獲得它:
$ sudo pacman -S autossh
下面在B 上做之前類似的事情,不同的是該隧道會由autossh 來維持:
B:
$ autossh -p 22 -M 6777 -NR 6766:127.0.0.1:22 [email protected]
-M 引數指定的埠用來監聽隧道的狀態,與埠轉發無關。
之後你可以在A 上通過6766 埠訪問B 了:
A:
$ ssh -p 6766 [email protected]
隧道的自動建立
然而這又有了另外一個問題,如果B 重啟隧道就會消失。那麼需要有一種手段在B 每次啟動時使用autossh 來建立SSH 隧道。很自然的一個想法就是做成服務,之後會給出在systemd 下的一種解決方案。
“打洞”
之所以標題這麼起,是因為自己覺得這件事情有點類似於UDP 打洞,即通過一臺在公網的機器,讓兩臺分別位於各自NAT 之後的機器可以建立SSH 連線。
下面演示如何使用SSH 反向隧道,讓C 連線到B。
首先在A 上編輯sshd 的配置檔案/etc/ssh/sshd_config,將GatewayPorts 開關開啟:
GatewayPorts yes
然後重啟sshd:
A:
$ sudo systemctl restart sshd
然後在B 上對之前用到的autossh 指令略加修改:
B:
$ autossh -p 22 -M 6777 -NR '*:6766:127.0.0.1:22' [email protected]
之後在C 上利用A 的6766 埠SSH 連線到B:
C:
$ ssh -p 6766 [email protected]
至此你已經輕而易舉的穿透了兩層NAT。
最終的解決方案
整合一下前面提到的,最終的解決方案如下:
首先開啟A 上sshd 的GatewayPorts 開關,並重啟sshd(如有需要)。
然後在B 上新建一個使用者autossh,根據許可權最小化思想,B 上的autossh 服務將以autossh 使用者的身份執行,以盡大可能避免出現安全問題:
B:
$ sudo useradd -m autosshB
$ sudo passwd autossh
緊接著在B 上為autossh 使用者建立SSH 金鑰,並上傳到A:
B:
$ su - autossh
$ ssh-keygen -t 'rsa' -C 'autossh@B'
$ ssh-copy-id [email protected]
注意該金鑰不要設定密碼,也就是執行ssh-keygen 指令時儘管一路回車,不要輸入額外的字元。
然後在B 上建立以autossh 使用者許可權呼叫autossh 的service 檔案。將下面文字寫入到檔案/lib/systemd/system/autossh.service,並設定許可權為644:
[Unit]Description=Auto SSH Tunnel
After=network-online.target
[Service]
User=autossh
Type=simple
ExecStart=/bin/autossh -p 22 -M 6777 -NR '*:6766:127.0.0.1:22' [email protected] -i /home/autossh/.ssh/id_rsa
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
[Install]
WantedBy=multi-user.target
WantedBy=graphical.target
在B 上設定該服務自動啟動:
B:
$ sudo systemctl enable autossh
如果你願意,在這之後可以立刻啟動它:
B:
$ sudo systemctl start autossh
然後你可以在A 上使用這條反向隧道穿透B 所在的NAT SSH 連線到B:
A:
$ ssh -p 6766 [email protected]
或者是在C 上直接穿透兩層NAT SSH 連線到B:
C:
$ ssh -p 6766 [email protected]
如果你對SSH 足夠熟悉,你可以利用這條隧道做更多的事情,例如你可以在反向連線時指定動態埠轉發:
C:
$ ssh -p 6766 -qngfNTD 7677 [email protected]
假設C 是你家中的電腦,A 是你的VPS,B 是你公司的電腦。如果你這樣做了,那麼為瀏覽器設定埠為7677 的sock4 本地(127.0.0.1)代理後,你就可以在家裡的瀏覽器上看到公司內網的網頁。