本文基于一次真实的生产部署整理:一台香港服务器 + 一台家庭服务器(无公网 IP), 用一条 SSH 隧道同时实现了「家庭出口走香港 IP」和「外网经香港 IP 访问家庭内网服务」。
一、SSH 隧道是什么
SSH 隧道(SSH Tunneling)就是把任意网络流量塞进一条已经建立好的 SSH 连接里传输。
SSH 协议本身支持多个”通道”(channel),这些通道在一条加密的 TCP 连接里多路复用。 隧道的本质,就是让远端的 sshd 进程充当”流量搬运工”:
- 它天然加密——流量在 SSH 的加密层里传输,外部看到的只是普通的 SSH 流量
- 它只需要一个端口——SSH 通了,隧道就通,不需要开放任何额外端口
- 这两点让它成为”端口受限环境”下的穿墙利器
┌────────┐ 加密的 SSH 连接(TCP) ┌────────┐
│ 客户端 │ ═══════════════════════════▶│ sshd │
│ ssh │◀─────────────────────────── ═│ 服务器 │
└────────┘ 通道多路复用 └────────┘
二、四种隧道类型
1. 本地转发 -L:把远端端口”搬到”本地
ssh -L [本地地址:]本地端口:目标地址:目标端口 user@server
原理:ssh 在本地监听一个端口;访问这个端口的流量经 SSH 连接送到远端, 由远端 sshd 转发给”目标地址:目标端口”。
你 ──▶ 本地:8080 ──┐ ┌──▶ 数据库:3306
║ SSH 加密 ║
sshd ────────┘
典型场景:访问跳板机背后的内网服务。
# 通过跳板机访问内网数据库,本地直接连 localhost:3306
ssh -L 3306:10.0.0.5:3306 user@jumpserver
关键细节:目标地址 是由远端 sshd 解析和发起连接的,
所以填 localhost 指的是远端自己,不是你的本机。
2. 远程转发 -R:把本地端口”发布”到远端
ssh -R [远端地址:]远端端口:目标地址:目标端口 user@server
原理:与 -L 正好相反——sshd 在远端监听端口,
访问远端端口的流量经隧道送回本地,由本地 ssh 转发给目标。
访客 ──▶ 服务器:8080 ──┐ ┌──▶ 你本机的服务:3000
║ SSH 加密 ║
ssh ◀────────┘
典型场景:内网机器(无公网 IP)主动连出去,把自己的服务发布到公网服务器上。 这就是”内网穿透”最朴素的形态。
# 在家里的机器上执行,把家里的 Web 服务挂到公网服务器的 8080
ssh -R 8080:localhost:3000 user@公网服务器
注意:-R 挂的是”端口级”转发,只支持 TCP 应用层流量;
公网服务器若要把端口开放给其他访客,sshd 需要 GatewayPorts yes。
3. 动态转发 -D:随身 SOCKS5 代理
ssh -D [本地地址:]本地端口 user@server
原理:本地 ssh 监听的端口是一个 SOCKS5 代理, 每个经过的应用层请求由远端 sshd 代为发起。流量按”会话”动态决定目标。
# 建立代理后,浏览器/支持 SOCKS5 的程序指向 localhost:1080
ssh -D 1080 user@server
典型场景:临时科学上网、让指定程序走远端网络出口。 相当于一条”按需建立”的隧道集合。
4. 三层隧道 -w:把 SSH 变成一条”虚拟网线” ⭐
ssh -w 本地TUN编号:远端TUN编号 user@server
原理:与上面三种本质不同——-L/-R/-D 是应用层端口转发(sshd 代替你连接目标),
而 -w 会在两端各创建一个 TUN 虚拟网卡,把 SSH 变成一条点对点的
IP 层(三层)隧道。两端可以通过虚拟 IP 互访,配合路由和 NAT 还能转发任意流量。
┌─────────────┐ ┌─────────────┐
│ 客户端 │ │ 服务器 │
│ 10.200.0.2 │ ════ SSH 加密 ════════▶ │ 10.200.0.1 │
│ [tun0] │ TUN 隧道 │ [tun0] │
└──────┬──────┘ └──────┬──────┘
│ ip route │ iptables NAT
改默认路由 转发到公网 / 内网服务
这是最强大的形态:ICMP、UDP、任意端口全都能跑,
-L/-R 做不到的事(如整段子网路由、非 TCP 协议)它都能做。
前提条件:
- 两端都要有
/dev/net/tun(modprobe tun) - sshd 开启
PermitTunnel yes(默认是 no) - 需要内核开启 IP 转发(
net.ipv4.ip_forward=1)才能跨网卡路由
三、进阶原理
TCP-over-TCP 问题
SSH 隧道本身跑在 TCP 上,如果隧道里再装 TCP 流量,就出现 TCP 套 TCP:
- 内层 TCP 丢包重传时,外层 TCP 也在丢包重传,重传被放大
- 网络质量差时性能急剧劣化(缓慢、卡顿)
这是 SSH 隧道(以及任何 TCP 承载的隧道)的先天短板。 WireGuard 等基于 UDP 的隧道没有这个问题——这也是为什么网络条件允许时优先选 WireGuard。
MTU 注意事项
隧道会封装加头,可用 MTU 变小。TUN 隧道通常设 1380~1400, 太大导致分片或黑洞(表现为”能 ping 通但大网页打不开”),太小浪费带宽。
保活与自动重连
SSH 连接断了隧道就断。生产环境必备三件套:
# ~/.ssh/config 客户端保活
ServerAliveInterval 15 # 每 15 秒发心跳
ServerAliveCountMax 4 # 4 次无响应判定断线
程序级方案:autossh(自动重连 ssh),或用 systemd 服务 + 循环脚本自行守护。
四、实战:香港服务器 IP 中转家庭 NAS(完整案例)
注:文中
203.0.113.10为文档专用示例 IP(RFC 5737),非真实地址,已替换脱敏。
需求
- 家庭服务器(内网 192.168.1.202,无公网 IP)
- 香港服务器(公网 203.0.113.10,SSH 在非标端口 5079)
- 目标①:家庭服务器的出站流量走香港 IP
- 目标②:外网访客通过香港 IP 访问家庭内网的 Web 服务
架构总览
互联网
│
┌───────────┴───────────┐
│ 香港服务器 203.0.113.10 │◀──── 外网访客
│ sshd(:5079) PermitTunnel │ http://203.0.113.10:5666
│ tun0: 10.200.0.1 │ │
│ iptables DNAT 5666───┐ │ │
└──────────────┬────────┼─┘ │
SSH 加密隧道 │ (转发) │
(TCP 5079) │ │
┌──────────────┴────────▼──────────────────┘
│ 家庭服务器 (内网)
│ tun0: 10.200.0.2
│ 默认路由 → 10.200.0.1 ← 出站走香港
│ 飞牛OS :5666 等 Web 服务
└──────────────┬──────────────
家用局域网
第 1 步:香港端开启隧道支持
# 加载 TUN 模块
modprobe tun
# /etc/ssh/sshd_config 中开启
PermitTunnel yes
# 重启 sshd(注意别把自己锁外面,先开个备用会话)
systemctl restart sshd
第 2 步:家庭端免密登录
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
ssh-copy-id -p 5079 root@203.0.113.10
# 香港端确认 PubkeyAuthentication yes(有些镜像默认关闭)
第 3 步:建立三层隧道(核心命令)
家庭端执行:
ssh -w 0:0 -p 5079 root@203.0.113.10 \
'ip addr add 10.200.0.1/30 dev tun0; ip link set tun0 up; \
ip forward 打开; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE'
# 本地脚本随后配置:
ip addr add 10.200.0.2/30 dev tun0
ip link set tun0 mtu 1380 up
ip route change default via 10.200.0.1 # 默认路由切进隧道
要点:
-w 0:0:两端各用 0 号 TUN 设备- 隧道内网用 /30(10.200.0.1 ↔ 10.200.0.2)或 /24 均可
- SSH 端点的路由要固定走原网关(否则隧道自己套自己,瞬间断网):
ip route add 203.0.113.10 via <原网关> - 香港端开
net.ipv4.ip_forward=1+MASQUERADE,家庭流量即可从香港 IP 出去
第 4 步:入站——香港端口转发进家庭
在香港端用 iptables DNAT,把公网端口”投递”进隧道对端:
iptables -t nat -A PREROUTING -p tcp --dport 5666 -j DNAT --to-destination 10.200.0.2:5666
iptables -I FORWARD 1 -d 10.200.0.2 -p tcp --dport 5666 -j ACCEPT
iptables -I FORWARD 1 -s 10.200.0.2 -j ACCEPT
iptables -t nat -A POSTROUTING -d 10.200.0.2 -p tcp --dport 5666 -j MASQUERADE
外网访客访问 http://203.0.113.10:5666 → DNAT 改写目标为 10.200.0.2 →
经 tun0 隧道送到家庭服务器 → 飞牛OS 响应原路返回。
第 5 步:守护与持久化
两端各一个 systemd 服务:
| 位置 | 服务 | 职责 |
|---|---|---|
| 家庭端 | hk-tunnel.service | 循环执行 ssh -w,断线重连;停止时恢复原路由 |
| 香港端 | home-relay.service | 开机重放 DNAT 转发规则(幂等脚本) |
家庭端守护脚本骨架:
#!/bin/bash
GW=$(ip route show default | awk '{print $3}') # 备份原网关
ip route add 203.0.113.10 via $GW # SSH 端点走原路
while true; do
ssh -w 0:0 -p 5079 -o ServerAliveInterval=15 -o ServerAliveCountMax=4 \
-o StrictHostKeyChecking=no root@203.0.113.10 \
'远端初始化命令'
# ssh 退出(断线)后循环重试
sleep 5
done
五、方法选型速查
| 需求 | 方案 |
|---|---|
| 临时访问远端/内网某个端口 | -L 本地转发 |
| 把内网服务发布到公网服务器(单端口) | -R 远程转发 |
| 让浏览器等程序走远端出口 | -D 动态转发(SOCKS5) |
| 两台机器组网、互访任意端口/子网、双向流量 | -w 三层隧道 |
| 高性能长期组网、UDP 载荷 | WireGuard(UDP 承载,无 TCP 套 TCP 问题) |
| 免开端口、要域名和 HTTPS、有 Cloudflare 账号 | Cloudflare Tunnel |
SSH 隧道的最佳定位:端口受限(只有 SSH 通)、临时性、或不想在两端装任何额外软件的场景。 一条 SSH 能干的事,就别引第三方。
六、常见坑
PermitTunnel默认是 no——-w报command not allowed先查它- 隧道递归——把默认路由切进隧道前,必须给 SSH 服务器 IP 加一条走原网关的静态路由
PubkeyAuthentication no——不少云镜像默认关闭公钥登录,守护脚本必须免密- MTU 黑洞——小包(ping/ssh)通、大包(网页/大文件)卡,九成是 MTU 没降
- iptables 规则不持久——重启即失,务必做成 systemd oneshot 服务重放
- nc/超时测试的假象——验证端口通不通,要用真实 HTTP 请求走一个来回, 单纯 TCP 握手成功(尤其经过代理时)不代表数据能通
评论
还没有评论,来留第一条吧。