- SSH 端口转发(SSH Tunneling 、Port Forwarding) 的理解与实验
- SSH Tunneling 就是 Port Forwarding(参考)
- SSH 端口转发的 3 种主要类型
- 实验假设
- 端口转发实验
- 查看端口转发列表
- 停止指定的端口转发
- 编程语言启动 ssh 端口转发
- 辅助技巧
- 参考
[TOC]
SSH Tunneling 就是 Port Forwarding(参考)
- SSH Tunneling 就是 Port Forwarding
- 中文可译作:SSH 隧道、SSH 端口转发
- 有时 SSH Tunneling 也被写成 SSH Tunnel
后面将简称
端口转发
或SSH 端口转发
典型用途:将本地端口转发到远程端口。
例如,将本地端口 8000 转发到远程 9000 端口,即可实现访问本地 http://localhost:8000 相当于访问远程的 http://10.10.10.11:9000 的服务。这在远程 IP 端口不能直接访问时特别有用。
3 种情况:
- 本地转发(Local Forwarding)
- 大致作用:转发本地端口到远程固定端口
- 动态转发(Dynamic Forwarding)
- 大致作用:转发本地端口到远程任意端口
- 远程转发(Remote Forwarding)
- 大致作用:转发远程端口到本地端口
大致原理可见下图:
假设有 3 个机器:
- A:
10.10.10.11
- B:
10.10.10.12
- C:
10.10.10.13
假设:
- A 与 B 可通讯
- B 与 C 可通讯
- A 与 C 不能通讯(能通讯也不影响实验)
A ---✓---> B ---✓---> C
A ---------x--------- C
- A 有 ssh 私钥(如
~/.ssh/id_rsa
),可 ssh 登录 C - A 已配置
~/.ssh/config
(后续用到):
Host hostB
# 请更换为 C 机器上的真正用户名
User userC
HostName 10.10.10.12
Port 22
IdentityFile ~/.ssh/id_rsa
注意:只有 -L
参数时,默认会顺便登录到对方机器,所以一般会增加 -fNT
参数
在 A 执行,启动端口转发:
解释:在 A 启动转发,将 A 的指定端口 4001,经过 B ,转发给 C 的指定端口 6001
可选:当然也可无需 C:在 A 启动转发,将 A 的指定端口,经过 B ,转发给 B 的指定端口(自行修改本实验 C 的 IP端口 为 B 的 IP端口)
# ---- 常用方式:启动到后台 ----
# L: `本地转发` 模式
# f: 让 ssh 命令跑到后台(Requests ssh to go to background just before command execution)
# N: 只进行端口转发,不执行远程命令(Do not execute a remote command)
# T: 不分配终端
# 模式:ssh -fNT -L localPort:targetIP:targetPort tunnelHost
ssh -fNT -L 4001:10.10.10.13:6001 userB@10.10.10.12 -p 22
# 或:若 ~/.ssh/config 已配置过 10.10.10.12 为某个名称如 `hostB`,则可以简写成:
ssh -fNT -L 4001:10.10.10.13:6001 hostB
# ---- 不常用:启动并登录到 B ----
# ssh -L 4001:10.10.10.13:6001 hostB
在 C 执行,启动简单 HTTP 服务:
# 在 C 启动一个简单 HTTP 服务,监听 6001 端口
python2 -m SimpleHTTPServer 6001
在 A 执行,验证结果:
# 验证:应能得到 C(即 10.10.10.13:6001)的响应
curl localhost:4001
在 A 执行,启动动态转发:
解释:在 A 启动转发,将 A 的指定端口,经过 B,转发到 B 可访问的任意端口
# D: `动态转发` 模式
# 模式:ssh -fNT -D localPort tunnelHost
ssh -fNT -D 4002 userB@10.10.10.12 -p 22
# 或
ssh -fNT -D 4002 hostB
在 B 执行,启动简单 HTTP 服务:
# 在 B 启动一个简单 HTTP 服务,监听 5001 端口
python2 -m SimpleHTTPServer 5001
在 C 执行,启动简单 HTTP 服务:
# 在 C 启动一个简单 HTTP 服务,监听 6001 端口
python2 -m SimpleHTTPServer 6001
# 在 C 启动一个简单 HTTP 服务,监听 6002 端口
python2 -m SimpleHTTPServer 6002
在 A 执行,验证结果:
# 验证:应能得到 B(10.10.10.12:5001)的响应
curl -x socks5://localhost:4002 10.10.10.12:5001
# 验证:应能得到 C(10.10.10.13:6001)的响应
curl -x socks5://localhost:4002 10.10.10.13:6001
# 验证:应能得到 C(10.10.10.13:6002)的响应
curl -x socks5://localhost:4002 10.10.10.13:6002
在 A 执行,启动远程转发:
解释:在 A 启动转发,使得 B 将任何对 B 的指定端口的访问,转发到 A 的指定端口
# R: 远程转发模式
# 模式:ssh -fNT -R remotePort:targetHost:targetPort remoteHost
ssh -fNT -R 5002:localhost:4003 userB@10.10.10.12 -p 22
# 或
ssh -fNT -R 5002:localhost:4003 hostB
在 A 执行,启动简单 HTTP 服务:
python2 -m SimpleHTTPServer 4003
在 C 执行,验证结果:
# 验证:应能得到 A(10.10.10.11:4003)的响应
# 在 C 执行 curl 请求到 B 的 5002 端口,最终得到了 A 的 4003 端口的响应
curl 10.10.10.12:5002
只要能访问到 10.10.10.12:5002 的都可以验证,例如,也可就在 B 本地访问 localhost:5002
在 B 执行(假设 B 已安装 Docker),启动一个 container:
# 启动 container
docker run -it python bash
在刚启动的 container 内查看 IP:
ip a|grep inet
# 假设得到 eth0 的 IP 为 172.17.0.4
在 container 内启动一个简单 HTTP 服务:
# 在 C 启动一个简单 HTTP 服务,监听 6003 端口
python2 -m SimpleHTTPServer 6003
在 A 执行,启动端口转发:
解释:在 A 启动转发,将 A 的指定端口 4004,经过 B ,转发给 B 的 Docker 启动的 container 内的端口 6003
# 模式:ssh -fNT -L localPort:targetIP:targetPort tunnelHost
ssh -fNT -L 4004:172.17.0.4:6003 hostB
在 A 执行,验证结果:
# 验证:应能得到 B 容器内的响应(即 172.17.0.4:6003)的响应
curl localhost:4004
知识点:
- container 无需暴露端口,B host 可直接通过
container IP
访问 container 的任意端口 targetIP:targetPort
是相对 B 来说的,而非相对 A
所以,猜测,VSCode 也是基于类似的机制将 container 内的端口转发到 local,而无需 container 绑定端口到其 host。
TODO,暂未找到快捷、精确的方式能列出所有 ssh 或非 ssh 进程启动的端口转发
在本地,查看由 ssh 进程列表(不一定是端口转发进程,且不会列出端口占用):
lsof -i -n | egrep 'ssh'
在本地,根据本地端口,查看占用该端口(如 4001 端口)的进程:
lsof -i -P -n | grep 4001
在本地执行:
# ---- 方式 01 :根据端口 kill 进程 ----
# 查看哪个进程占用了端口 4001
lsof -i -P -n | grep 4001
kill 查出来的PID
# 最粗暴的方式:kill 掉所有 ssh 进程
# 会 kill 掉:ssh 登录、ssh 端口转发进程
# 但不会 kill 掉非 ssh 进程启动的端口转发
pkill ssh
启动 ssh 端口转发,不一定必须 ssh 命令,也可以通过编程语言实现(只需遵循相关协议):
Python 启动简易 HTTP 服务器:
# Python 2,端口:5001
python -m SimpleHTTPServer 5001
# Python 3,端口:6001
python3 -m http.server 6001
查看占用端口的进程:
# 查看哪个进程占用了端口 5001
lsof -i -P -n | grep 5001