SSH 隧道还能干什么

SSH 隧道还能干什么

先建立一张“能力地图”,后面所有用法都是这张图上的组合:

能力 命令 本质
定向端口转发 -L / -R 一对一 TCP 搬运
动态代理 -D 一对任意 TCP(本地 SOCKS5)
netcat 模式 -W host:port 把隧道当 nc 用,输出到 stdout
Unix 套接字转发 -L sock:sock / -R 把 socket 文件当端口
认证转发 -A(ForwardAgent) ssh-agent 的 socket 被转发
图形转发 -X / -Y X11 协议走隧道
链路拼接 -J / ProxyCommand 隧道套隧道

下面按“实际想解决的问题”来讲。


一、代理与统一出口(-D 的战场)

-D 开的是本地 SOCKS5,目标不写死,所以它的用途比 -L 更广:

1. 让工具链走远端出口

ssh -N -D 1080 me@jump  
  
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me  
all_proxy=socks5h://127.0.0.1:1080 npm install  
git -c http.proxy=socks5h://127.0.0.1:1080 clone https://github.com/x/y  

注意用 socks5h(--socks5-hostname),让 DNS 也在远端解析,否则域名在你本地解析、流量走远端,既可能解析失败也泄露意图。

2. 固定出口 IP 过白名单

第三方 API 只放行某个固定公网 IP,而你本地是动态 IP:

ssh -N -D 1080 me@fixed-ip-vps  
# 所有请求从 vps 的 IP 出去  

比买代理、配 NAT 网关都轻。

3. 浏览器分流

Firefox 直接支持 SOCKS5 代理(配合 FoxyProxy 做规则分流),Chrome 需要 --proxy-server 或系统代理。可以只让某几个域名走隧道。

4. 反向 SOCKS:把“你的机器”变成出口

-D 只能在本机开 SOCKS。想在远端开 SOCKS,就把它和 -R 拼起来:

ssh -N -D 1080 -R 1080:localhost:1080 me@vps  
# 现在 vps 上的 127.0.0.1:1080 是一个 SOCKS,出口是你的机器  

这是“让你身边的网络位置被远端使用”的常用手法。


二、给裸奔的协议套一层加密

这是被严重低估的用途。很多服务协议本身明文、无认证或弱认证:

协议 明文风险 隧道做法
Redis 无认证/明文,暴露即被挖矿 ssh -L 6379:127.0.0.1:6379 me@cache
MySQL/PG 认证可降级、流量可嗅探 ssh -L 15432:127.0.0.1:5432 me@db
VNC / RDP 弱加密甚至明文 ssh -L 5901:127.0.0.1:5901 me@desktop
LDAP 明文绑定密码 ssh -L 389:dc:389 me@jump
Elasticsearch / Prometheus 常无认证 ssh -L 9200:es:9200 me@jump
云 metadata 169.254.169.254 只在内网可达 ssh -L 8888:169.254.169.254:80 me@ec2

注意一个常被忽略的事实:SSH 隧道只加密“你到跳板机”这一段。跳板机到目标那一跳仍是明文——如果那段网络也不可信,隧道救不了你。


三、访问只监听 127.0.0.1 的 Web 控制台

这是日常最高频的场景,几乎每个数据/ML/运维工具都属于这一类:

ssh -N -L 8888:localhost:8888 me@gpu-box     # Jupyter  
ssh -N -L 6006:localhost:6006 me@gpu-box     # TensorBoard  
ssh -N -L 5000:localhost:5000 me@ml-server   # MLflow  
ssh -N -L 8001:localhost:8001 me@k8s-master  # k8s Dashboard  
ssh -N -L 3000:localhost:3000 me@monitor     # Grafana  
ssh -N -L 9090:localhost:9090 me@monitor     # Prometheus  
ssh -N -L 9000:localhost:9000 me@ci          # Portainer / ArgoCD  

一次可以开多个:

ssh -N -L 8888:localhost:8888 -L 6006:localhost:6006 -L 9090:localhost:9090 me@gpu-box  

Kubernetes 里的 kubectl port-forward 本质就是这类东西(走 API Server 隧道而不是 SSH):

kubectl port-forward -n monitoring svc/grafana 3000:80  

四、跳板与多跳(隧道套隧道)

1. ProxyJump(推荐的现代写法)

ssh -J me@jump me@target          # 相当于先连 jump,再从 jump 连 target  
ssh -J me@a,me@b,me@c me@target   # 多级  

它在 ~/.ssh/config 里长这样:

Host target  
    HostName 10.0.3.21  
    User me  
    ProxyJump jump.example.com  

2. -W netcat 模式

ssh -W 10.0.3.21:5432 me@jump  

这会把一条裸 TCP 流打到 stdout,等于“用 SSH 认证的 nc”。它的价值在于可以塞进任何期望“ProxyCommand”的地方:

ssh -o "ProxyCommand=ssh -W %h:%p me@jump" me@target  

3. 让别的工具也走跳板

很多工具不认识 SSH,但它们认识 ProxyCommand 或者能借 SSH 的壳:

rsync -av -e "ssh -J me@jump" ./data/ me@target:/data/  
scp -o ProxyJump=me@jump ./a.txt me@target:/tmp/  
git -c core.sshCommand="ssh -J me@jump" clone me@target:/repo  

Git 的 ssh://、Docker 的 DOCKER_HOST=ssh://me@host、Ansible 的 ansible_ssh_common_args 都属于同一思路:把 SSH 当通用传输层。

4. 借用第三方网络位置

ssh -N -L 3306:mysql.prod:3306 me@jump   # 只要能连到,目标不必在跳板机上  

这在“我本机被墙/被限,但跳板机通”时特别有用。


五、内网穿透与反向暴露(-R 的战场)

1. 常驻反向隧道(自建 ngrok)

autossh -M 0 -N -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes \  
  -R 0.0.0.0:9000:localhost:3000 me@vps  

配 systemd 就能开机自启、断线自愈:

[Unit]  
Description=Reverse tunnel to vps  
After=network-online.target  
  
[Service]  
User=me  
ExecStart=/usr/bin/autossh -M 0 -N -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -R 0.0.0.0:9000:localhost:3000 me@vps  
Restart=always  
  
[Install]  
WantedBy=multi-user.target  

2. 本地调试外部 Webhook

Stripe / GitHub / 微信 / 支付的回调没法打到你的笔记本,用 -R 造一个公网回调地址:

ssh -N -R 0.0.0.0:8080:localhost:3000 me@vps  
# 回调地址填 http://vps:8080/webhook  

3. 远程协助:反向进入客户内网

在客户机器上执行(他主动连出,你不需要任何入站权限):

ssh -N -R 2222:localhost:22 support@my-vps  
# 你在 vps 上:ssh -p 2222 user@127.0.0.1 即进客户机器  

4. IoT / 边缘设备维护

树莓派、工控机、门店路由器在内网,没有公网 IP,靠反向隧道维持一条可维护通道。

5. 临时协作

ssh -N -R 8080:localhost:8080 demo@demo-server   # 把 demo-server:8080 发给同事  

六、文件与挂载

scp/sftp 本身就跑在 SSH 上,不需要额外隧道;但隧道的链路复用能力很有用:

1. 把远端目录挂到本地

sshfs me@host:/data /mnt/remote -o reconnect,ServerAliveInterval=15  
fusermount -u /mnt/remote  

配合 ProxyJump 也能挂隔着跳板机的目录。

2. 连接复用,避免反复握手

Host *  
    ControlMaster auto  
    ControlPath ~/.ssh/cm/%r@%h:%p  
    ControlPersist 10m  

之后 ssh、scp、rsync、git 全部复用同一条底层连接,速度提升非常明显(尤其是有 2FA 或高延迟链路时)。

3. 原地流式搬运(不落盘中转)

ssh me@a "tar czf - /data" | ssh me@b "tar xzf - -C /"  

这是“用 SSH 当传输管道”的经典写法,不经过本地磁盘。


七、认证与图形转发

1. -A Agent 转发

把本机的 ssh-agent socket 转发到远端,这样在跳板机上继续 ssh/git 时不用把私钥拷过去:

ssh -A me@jump  
jump$ ssh-add -l      # 能看到你的 key  
jump$ git clone git@github.com:x/y  

⚠️ 风险很大:跳板机上拥有 root 的人,在你连接期间可以用你的 agent 冒充你(SSH_AUTH_SOCK 指向那个 socket 就行)。缓解手段:

  • 优先用 ProxyJump 代替 -A(私钥永远不出本机);
  • 必须用时加 ssh-add -c(每次使用需确认);
  • 用 ssh-add -t 300 给 key 设过期时间;
  • 只在可信主机上 -A,config 里显式按 Host 开启而不是 Host *。

2. -X / -Y X11 转发

ssh -X me@host      # 不可信模式,有限制  
ssh -Y me@host      # 可信模式,等价于允许远端操纵你的 X server(有风险)  

用来看远端 GUI 程序(MATLAB、Wireshark、virt-manager)。要高帧率或复杂图形,改用 VNC/XRDP 走 -L 通常更好。

3. Unix socket 转发(OpenSSH 6.7+)

# 把远端的 docker.sock 拉到本地(危险,等于给远端 root)  
ssh -L /tmp/docker.sock:/var/run/docker.sock me@host  
DOCKER_HOST=unix:///tmp/docker.sock docker ps  
  
# 转发 gpg-agent / ssh-agent socket  
ssh -R /tmp/remote-gpg.sock:/run/user/1000/gnupg/S.gpg-agent me@host  
  
# 本地 socket → 远端端口  
ssh -L /tmp/x.sock:localhost:8080 me@host  

如果 socket 已存在导致绑定失败,服务端加 StreamLocalBindUnlink yes。


八、把 SSH 当通用传输层

1. UDP over SSH(SSH 不支持 UDP,只能手工桥)

SSH 隧道只搬 TCP。要过 UDP,就在两端各放一个转换器:

# 远端(跳板机上)把 TCP 5353 转成到 8.8.8.8 的 UDP  
jump$ socat TCP-LISTEN:5353,reuseaddr,fork UDP:8.8.8.8:53  
  
# 本地建隧道  
ssh -N -L 5353:localhost:5353 me@jump  
  
# 本地用 TCP 去问  
dig +tcp @127.0.0.1 -p 5353 example.com  

2. sshuttle:基于 SSH 的“穷人 VPN”

sshuttle -r me@jump 0.0.0.0/0 --dns  

它不需要在远端装东西(只需 python3),本地用 iptables/pf 拦截流量,把 TCP + DNS 全部塞进一条 SSH 连接。适合“我要让整个系统像在远端网络里一样”,且不想配 VPN 的场景。缺点:只支持 TCP + DNS,UDP 和 ICMP 不过。

3. 端口转发做串口/设备桥

ssh -L 5000:/dev/ttyUSB0 me@host    # 不直接支持设备,需 socat 帮忙  
# 正确做法:  
jump$ socat TCP-LISTEN:5000,reuseaddr,fork /dev/ttyUSB0,raw,echo=0  
ssh -N -L 5000:localhost:5000 me@jump  

九、在受限网络里让 SSH 活着

出站 22 被封,或被 HTTP 代理卡住:

# sshd 监听 443(最简单,但和企业 HTTPS 冲突)  
ssh -p 443 me@host  
  
# 走 HTTP CONNECT 代理  
ssh -o "ProxyCommand=proxytunnel -p proxy:8080 -d %h:%p" me@host  
ssh -o "ProxyCommand=nc -X connect -x proxy:8080 %h %p" me@host  
  
# 套 TLS,伪装成普通 HTTPS(服务端用 stunnel 转回 22)  
ssh -o "ProxyCommand=openssl s_client -quiet -connect host:443 -servername host" me@host  

对比一下:frp、ngrok、Cloudflare Tunnel、Tailscale 是这些手法的产品化封装,优势在 NAT 穿透、多路复用、可观测性;SSH 的优势是零额外依赖、零成本、随时可用。


十、CI/CD 与容器场景

1. CI runner 反向暴露给外部

一些云服务需要回调 CI 环境时,用 -R 开个临时口。

2. 本地连生产集群里的服务

ssh -N -L 8080:argocd-server.argocd.svc:443 me@k8s-node  

3. Docker 远程

DOCKER_HOST=ssh://me@host docker ps  

这是 Docker 自带的 SSH 传输(内部就是 ssh -W 那套思路),比开 2375 明文端口安全得多。


十一、反过来:怎么管住这些能力

正因为 SSH 隧道太好用,它也是数据外泄、内网横向、绕过审计的经典通道。所以服务端要知道怎么关:

# /etc/ssh/sshd_config  
AllowTcpForwarding no                # 全关  
AllowTcpForwarding local             # 只允许 -L(禁止 -R)  
PermitOpen db.internal:5432          # 只允许转发到指定目标  
PermitListen 127.0.0.1:9000          # 限制 -R 可监听的地址  
GatewayPorts no                      # 禁止 -R 对外开放  
X11Forwarding no  
AllowAgentForwarding no  

还可以按用户/组精细化:

Match Group contractors  
    AllowTcpForwarding no  
    PermitOpen none  

监控侧看什么:sshd 日志里的 Local connections to ... forwarded to / Remote connections from ... forwarded to,以及异常的长时间空闲连接、大流量单向连接。


十二、必须知道的局限

局限 说明 应对
只搬 TCP UDP/ICMP 过不去 socat 桥接、sshuttle、或改用 WireGuard
单流受限于一条 TCP 高延迟链路上吞吐明显低于裸连,多流并发容易互相拖累 开 ControlMaster 复用、-C、换 aes128-gcm、或拆多条隧道
断线即断隧道 无自动重连 ServerAliveInterval + autossh/systemd
最后一跳不加密 跳板机到目标那段仍是明文 目标侧自己上 TLS
DNS 泄漏 用 socks5 而非 socks5h 时域名在本地解析 用 socks5h 或远端解析
-R 静默降级 GatewayPorts no 时对外绑定请求被悄悄改成 127.0.0.1 服务端显式配 clientspecified,并用 ss -lntp 验证
绑定失败不报错 端口被占用时 SSH 仍然登录成功 ExitOnForwardFailure yes

十三、速查表

我想…… 用
访问内网数据库/服务 -L
访问只监听 127.0.0.1 的控制台 -L(可用 -N -f)
让工具走远端出口 / 固定出口 IP -D + socks5h
把本地服务暴露到公网 -R + GatewayPorts clientspecified
给裸奔协议加密 -L
穿过堡垒机连更深一层 -J / ProxyCommand ssh -W
整个系统当在远端网络里 sshuttle
挂载远端目录 sshfs(+ ProxyJump)
不拷私钥却能在远端用 git -A 或(更好)ProxyJump
看远端 GUI 程序 -X / -Y
转发 socket 文件(docker/gpg/agent) -L sock:sock / -R
穿透 UDP socat 两端桥接
出站 22 被封 443 端口 / proxytunnel / stunnel
把 SSH 连接复用给 scp/rsync/git ControlMaster + ControlPersist

一句话总结:SSH 隧道真正的价值不是“转发端口”,而是它提供了一个零依赖、带认证、加密的通用 TCP 传输层。凡是“我这里不通、那边通”的网络问题,几乎都能用它拼出一个解。