前言
平時經常遇到下面的情況:
- 工作沒做完,想回家繼續做。帶電腦回家?
- 碰到以前做過的功能,想從家里的電腦撈一點代碼。沒獨立 IP 怎么訪問?
- 服務器只允許公司網絡訪問,在外面要登服務器。找領導設置安全組權限 ( 或者防火墻 ) ?
- 在外面想訪問公司局域網中的某個服務,比如 Gitlab ?
- 公司網絡不能逛淘寶?
- 想去墻外的世界看一看?
一般情況下的對應方法:
- 遠程連接 ( 比如 Windows 自帶的 )
- NAT 內網穿透 ( 比如花生殼,幾年前用過免費版的,不太穩定,Linux 下安裝也不太順暢 )
- 一般需要正當理由
- 公司有固定公網 IP 的話,路由器設置端口轉發,不過路由器權限呢?
- 代理服務器
- VPN ( 作用和代理服務器差不多,但原理不一樣 )
本文的目的就是使用 SSH TUNNEL 這項技術解決上面的問題,也就是打通所謂的 "公司內網" 和 "家庭內網"。
SSH TUNNEL 簡介
經常遠程連接 Linux 服務器的小伙伴,對于 ssh 這個命令應該不陌生吧。它還可以不執行遠程指令只建立一條和遠程服務器之間的連接,也就是形成了一條通道,在這條通道上我們就可以做一些文章了。
先看下 ssh 命令的參數介紹 ( 需要了解更多請通過命令 man ssh
查看 )
-1: 強制使用ssh協議版本1;
-2: 強制使用ssh協議版本2;
-4: 強制使用IPv4地址;
-6: 強制使用IPv6地址;
-A: 開啟認證代理連接轉發功能;
-a: 關閉認證代理連接轉發功能;
-b: 使用本機指定地址作為對應連接的源IP地址;
-C: 請求壓縮所有數據;
-F: 指定ssh指令的配置文件;
-f: 后臺執行ssh指令;
-g: 允許遠程主機連接主機的轉發端口;
-i: 指定身份文件;
-l: 指定連接遠程服務器登錄用戶名;
-N: 不執行遠程指令;
-o: 指定ssh客戶端配置選項來覆蓋配置文件中的配置,比如: -o "StrictHostKeyChecking no" 可以去除檢查主機的提示;
-p: 指定遠程服務器上的ssh端口(默認為22);
-q: 靜默模式;
-X: 開啟X11轉發功能;
-x: 關閉X11轉發功能;
-y: 開啟信任X11轉發功能。
-D: 監聽本地的指定端口,本機將作為 SOCKS5 服務器使用
-L: 本地監聽指定端口,該端口收到的請求會通過遠程服務器轉發到另一臺機器的指定端口上
-R: 遠程服務器上監聽指定端口,該端口收到的請求會通過本地轉發到另一臺機器的指定端口上
關鍵的就是最后的三個參數,對應 SSH 隧道 的三種端口轉發模式: 本地轉發 ( -L Local ),遠程轉發 ( -R Remote ) 和動態轉發 ( -D Dynamic )
本地轉發
本地監聽指定端口,該端口收到的請求會通過遠程服務器轉發到另一臺機器的指定端口上
-
語法:
ssh [-p ssh_port] -L [bind_address:]port:host:hostport user@remote_host 解釋: port: 本地監聽的端口 bind_address: 監聽端口使用的本地地址 ( 如: 192.168.1.2,127.0.0.1,0.0.0.0 ), 不設置時默認為回環地址 ( 127.0.0.1 ) host: 轉發的目標 IP hostport: 轉發的目標端口 注意: host:hostport 是遠程主機 user@remote_host:ssh_port 所能訪問到的地址 ( 包括 user@remote_host 自己 ),本地主機不一定能訪問該地址
-
示例:
ssh -p 23 -Nf -L 0.0.0.0:3000:123.123.123.124:3001 root@123.123.123.123 說明: 1. 通過 23 端口 ssh 遠程服務器 123.123.123.123,使用帳號 root 登錄 2. 本地監聽端口 0.0.0.0:3000 3. 123.123.123.124 和 123.123.123.123 在一個局域網下,而本地不能直接訪問 123.123.123.124 4. 123.123.123.124 主機通過 3001 端口開放服務 5. 調用鏈路: 本地訪問 --> 127.0.0.1:3000 -- ssh 隧道 --> 123.123.123.123 -- 轉發 --> 123.123.123.124:3001
上面示例中相當于將遠程主機 123.123.123.124 的 3001 端口映射為本地的 3000 端口,這樣就可以通過局域網訪問被限制的公網服務了。
遠程轉發
遠程服務器上監聽指定端口,該端口收到的請求會通過本地轉發到另一臺機器的指定端口上
-
語法:
ssh [-p ssh_port] -R [bind_address:]port:host:hostport user@remote_host 解釋: 語法和 -L 一樣,只不過監聽側和目標側的網絡對換而已 port: 在遠程主機 user@remote_host 上監聽的端口 bind_address: 遠程主機 user@remote_host 監聽端口使用的地址 ( 如: remote_host, 127.0.0.1, 0.0.0.0 )。和 -L 有點區別,指定 IP 需要在遠程主機上修改 /etc/ssh/sshd_config 中的配置: GatewayPorts。 "yes" 表示 強制為 0.0.0.0; "no" 表示 強制為回環地址 ( 127.0.0.1 ); ( 默認 ) "clientspecified" 表示 由客戶端決定; host: 轉發的目標 IP hostport: 轉發的目標端口 注意: host:hostport 是本地主機所能訪問到的地址 ( 包括本機 ),遠程主機 user@remote_host:ssh_port 不一定能訪問該地址
-
示例:
ssh -p 23 -Nf -R 172.17.0.1:3000:192.168.1.100:3001 root@123.123.123.123 說明: 1. 通過 23 端口 ssh 遠程服務器 123.123.123.123,使用帳號 root 登錄 2. 123.123.123.123 監聽端口 172.17.0.1:3000 ( 我的遠程服務器運行著 docker 所以有一個虛擬網絡 172.17.0.0 ) 3. 192.168.1.100 和 本地主機 在一個局域網下,而 123.123.123.123 ( 外網 ) 不能直接訪問 192.168.1.100 ( 內網 ) 4. 192.168.1.100 主機通過 3001 端口開放服務 5. 調用鏈路: 遠程服務器中訪問 --> 172.17.0.1:3000 -- ssh 隧道 --> 本地 -- 轉發 --> 192.168.1.100:3001
上面示例中相當于將局域網主機 192.168.1.100 的 3001 端口映射為遠程服務器 123.123.123.123 的 3000 端口,這樣就可以通過公網來訪問局域網中的服務了。
動態轉發
監聽本地的指定端口,本機將作為 SOCKS5 服務器使用
-
語法:
ssh [-p ssh_port] -D [bind_address:]port user@remote_host 解釋: port: 本地監聽的端口 bind_address: 監聽端口使用的本地地址 ( 如: 192.168.1.2, 127.0.0.1, 0.0.0.0 ), 不設置時默認為回環地址 ( 127.0.0.1 ) 注意: 啟動 socks5 代理 代理測試示例: curl --socks5 bind_address:port baidu.com
-
示例:
ssh -p 23 -Nf -D 127.0.0.1:3000 root@123.123.123.123 說明: 1. 通過 23 端口 ssh 遠程服務器 123.123.123.123,使用帳號 root 登錄 2. 本地監聽端口 127.0.0.1:3000 3. 調用鏈路: 本地局域網中訪問任意 http 服務 -- 使用代理 --> 172.17.0.1:3000 -- ssh 隧道 --> 123.123.123.123 -- 轉發 --> 目標 http 服務
上面示例中相當于開啟了一個 代理服務器 ,比起 本地轉發 需要指定具體端口來說更加方便。
正文
上面已經介紹了 SSH TUNNEL 的使用方式,接下來就開始干正事了,先上兩張網絡拓撲圖。
對比兩張圖把少的線連上就好了,是不是很 easy ?
原先環境:
三個獨立的網絡: 公司內網、家里內網、阿里云專有網絡,其中可以通過 公司網絡 訪問 阿里云 上的服務器而 家里網絡 則不行。-
目標:
- 使 家里網絡 的主機能訪問 公司內局域網 中的主機
- 使 家里網絡 的主機能訪問 公司阿里云 上的服務器
-
預備工作:
-
有一臺公網的服務器作為 跳板機 ,公司和家里都能訪問到它
( 沒有服務器?阿里云騰訊云學生機弄臺玩玩 )
-
因為 ssh 命令不能添加密碼作為參數,只能手動輸入,而且每次連接都要重新輸入也不是很方便,這里采用 公鑰方式 登錄就可以免輸密碼了
# 創建 ssh 密鑰對 ssh-keygen -t rsa -b 4096 -C "備注1" # 遠程服務器上的 ~/.ssh/authorized_keys 權限要是 600 # 追加本機公鑰到遠程服務器 cat ~/.ssh/id_rsa.pub | ssh -p 23 root@123.123.123.123 "cat - >> ~/.ssh/authorized_keys" # 或者直接復制本機公鑰到遠程服務器 ( 會覆蓋掉遠程服務器已有的其他公鑰 ) scp -P 23 -p ~/.ssh/id_rsa.pub root@123.123.123.123:~/.ssh/authorized_keys
-
-
步驟:
-
將 公司主機A 的端口 22 映射到 跳板機J 的端口 9999 上, 在 公司主機A 按照下面的命令配置 ( 跳板機J 需要按上文中的方式將 GatewayPorts 設為
true
)ssh -p 23 -Nf -R 9999:127.0.0.1:22 root@123.123.123.123
當然也可以通過 docker 運行 autossh 來使這個隧道可以自動重連
# 暴露本地 ssh 端口到遠端服務器端口 ssh-to-expose-ssh-server: image: jnovack/autossh container_name: autossh-ssh-to-expose-ssh-server # 使用宿主機的網絡 network_mode: host environment: - SSH_HOSTUSER=root - SSH_HOSTNAME=123.123.123.123 - SSH_HOSTPORT=23 - SSH_TUNNEL_REMOTE=9999 - SSH_TUNNEL_HOST=127.0.0.1 - SSH_TUNNEL_LOCAL=22 restart: always volumes: - ~/.ssh/id_rsa:/id_rsa
這樣在 跳板機J 上就可以通過
ssh -p 9999 root@127.0.0.1
遠程連接到 公司主機A 了 -
在 跳板機J 上設置動態轉發使其作為 代理服務器 , 將接收到的請求再轉發到 公司主機A
在 跳板機J上執行下面的命令
ssh -p 9999 -Nf -D 0.0.0.0:1080 root@127.0.0.1
同樣的,要把 跳板機J 的 公鑰 復制到 公司主機A 上實現免密登錄
# 創建 ssh 密鑰對 ssh-keygen -t rsa -b 4096 -C "備注2" # 追加本機公鑰到遠程服務器 cat ~/.ssh/id_rsa.pub | ssh -p 9999 root@127.0.0.1 "cat - >> ~/.ssh/authorized_keys"
當然也可以使用 docker 啟動 autossh
# 使用 SSH TUNNEL 創建 SOCKS5 代理 ssh-tunnel-socks5-server: image: jnovack/autossh container_name: ssh-tunnel-socks5-server entrypoint: autossh -M 0 -N -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -o "ExitOnForwardFailure yes" -o "StrictHostKeyChecking no" -t -t -i /id_rsa -D 0.0.0.0:1080 -p 9999 # 使用宿主機的IP root@172.17.0.1 ports: - "1081:1080" restart: always volumes: - ~/.ssh/id_rsa:/id_rsa
這樣 家里主機F 就可以通過設置代理 跳板機J 連接上 阿里云 上的服務器了。
原理: 公司主機A 和 跳板機J 聯合組成 代理層 。調用鏈路為: 家里主機F -- 使用代理 --> 跳板機J -- ssh 隧道 --> 公司主機A -- 轉發 --> 阿里云 服務器。
注 : 組成 代理層 還有另一種方式: 公司主機A 開代理服務同時把端口遠程暴露到 跳板機J 上, 通過這種方式就可以配置代理服務的密碼了, 具體配置參照 這里 。
-
就這么兩步配置已經打通"公司網絡"和"家里網絡"了。不過,貌似實現的效果是單向的誒 ( 只能家里連公司,公司還不能連家里, 如下圖所示 )
解決辦法: 家里主機F 按 公司主機A 的步驟再配一遍就好啦。
拓展
-
使用代理訪問受限制的網站 ( 比如公司內網的項目 ) :
由于代理服務器使用的是 SOCKS5 協議,Chrome 上安裝 SwitchyOmega 插件配置代理服務器地址,即可通過代理服務器訪問受限制的服務了。
-
將 SOCKS5 協議轉為 HTTP 協議,然后通過系統自帶的方式 ( Win 下設置 IE 的 Internet 屬性 ,移動設備設置 WIFI 的 HTTP 代理 ) 來設置全局代理,docker-compose 配置如下
# 使用 gost 代理 SOCKS5 端口 gost-server: image: ginuerzh/gost command: -L=:8080 -F=socks5://ssh-to-local-proxy:1080 ports: - "1082:8080" restart: always # 使用 privoxy 代理 SOCKS5 端口 privoxy: image: rdsubhas/tor-privoxy-alpine entrypoint: sh -c 'echo "listen-address 0.0.0.0:8118" > /etc/service/privoxy/config && echo "forward-socks5 / ssh-to-local-proxy:1080 ." >> /etc/service/privoxy/config && privoxy --no-daemon /etc/service/privoxy/config' ports: - "1083:8118" restart: always
這里給出兩種轉換方式 : gost 和 privoxy , 選擇其中一種方式即可。privoxy 找不到純凈的鏡像, 都是捆綁 tor 的, 就挑一個最精簡的來做修改吧。
其實還可以在 公司主機A 搭建 SS 服務器 , 然后就可以通過 SS 客戶端 來連接代理。具體的本文就不細說了,搭建示例可以看 這里 對應的 compose 文件。
終端工具中使用代理來連接云服務器
這里的例子使用 WinSCP , 其他工具應該也差不多: 新建站點 -> 編輯 -> 高級 -> 連接
- ssh 隧道方式 : 使用 跳板機J 的 9999 端口加 公司主機A 的帳號、密碼、端口。
- 代理方式 : 可以選擇 SOCKS5 協議或者 HTTP 協議,和上面的配置一致即可。
其他問題
- SSH 連接可能會中斷 ( 臨時的網絡擁塞、SSH 超時、中繼主機重啟等等 ),所以可以借助 autossh 實現自動重連來保證可靠的服務。
- SSH 連接有 超時斷開 的機制,WinSCP 有時會不斷提示 斷開重連 ,有兩種方式來維持連接:
- 設置 -> 面板 -> 遠程 -> 修改刷新面板間隔為 30s 或者更短
- session 設置 -> 高級 -> 連接 -> keepalives -> 執行啞命令 ( 空 SSH 包是沒效果的 )
-
Win10 1803 已經默認集成了 SSH 工具 ,可以直接使用上面的命令。當然也可以借助終端工具來建立 SSH 隧道 ,比如在
MobaXterm
上可以使用圖形界面進行設置,比較直觀。 - 文中的方法使用 兩級代理三次轉發 ( 如果在 docker 中運行那就是四次轉發 ) 實現不同網絡的訪問,中間代理層的 帶寬 就限制了整體的訪問速度,需要注意下。
- 跳板機 提供的 SOCKS5 代理服務沒有密碼,如果被別人知道了 IP 和端口容易被惡意攻擊,所以一定要做好安全措施 ( 比如: 安全組 或 防火墻 限制 跳板機 的入網 IP 為自己指定的 IP ) 。
- 你公司其他同事也想連你家里的電腦?總不能把 跳板機 暴露給他吧,太不安全了,你本地再開一層代理,讓他用你的電腦作為代理好了。
倉庫
基于 docker-compose 運行的完整配置已經上傳至 GitHub 。
參考文章
- SSH隧道技術----端口轉發,socket代理
- 通過 SSH 實現 TCP / IP 隧道(端口轉發):使用 OpenSSH 可能的 8 種場景
- winScp如何通過隧道代理進行遠程連接
- ssh tunnel 三種模式
- SSH Tunnel | SSH.COM
- SSH port forwarding - Example, command, server config | SSH.COM