标准 OpenSSH 协议本身仅支持 TCP 端口转发,无法直接转发 UDP 流量。在实际工程中,通用解决方案是通过 socat 工具在隧道两端进行协议转换(UDP 转 TCP 再转回 UDP),或采用 SSH3 等新协议(尚未普及)。以下方案基于 OpenSSH + socat 组合,适用于生产环境。
先说结论:常规 SSH 命令无法直接转发 UDP,需借助 socat 进行协议转换。
- 适合场景:需要加密传输 UDP 业务(如 DNS、QUIC 测试、游戏服代理)且无法直接开放端口的场景
- 核心依赖:两端服务器安装 openssh-client/server 及 socat 工具
- 关键配置:SSH 服务端需开启
GatewayPorts以允许非本地绑定,socat 需配置守护进程 - 验收标准:使用
nc -u从隧道入口发送数据,目标端能准确收到
场景说明:我想访问远程 UDP 还是暴露本地 UDP?
UDP 过 SSH 隧道主要有两种流向,配置命令不同,请先明确需求:
- 场景 A(本地转发 -L):本地客户端想访问远程服务器背后的 UDP 服务。
- 场景 B(远程转发 -R):远程服务器想访问本地客户端背后的 UDP 服务(本文以此为例)。
若选择场景 B,流量走向为:服务端用户 → 服务端 Socat(UDP) → SSH 隧道(TCP) → 客户端 Socat(UDP) → 目标 UDP 服务。
SSH 服务端必要配置检查
SSH 远程转发(-R)默认仅绑定 127.0.0.1,若需服务端 Socat 稳定连接或允许外部接入隧道端口,需修改服务端配置。
1. 编辑配置文件
在 SSH 服务端执行:
sudo vi /etc/ssh/sshd_config2. 开启 GatewayPorts
找到或添加以下配置项,允许远程转发绑定到非本地接口:
GatewayPorts yes3. 重启服务
执行 sudo systemctl restart sshd 使配置生效。若无法修改服务端配置,则 SSH 命令中不能使用 * 绑定符,且 Socat 必须在服务端本地运行。
命令速用版
以下命令基于场景 B(暴露本地 UDP 到远程)。假设目标 UDP 服务运行在客户端网络内,IP 为 <Target_IP>,端口为 <Target_Port>。
1. 客户端(建立隧道与协议还原)
先建立 SSH 隧道,再启动 socat 监听隧道端口并转发至目标 UDP:
# 建立隧道(-N 不执行命令,-f 后台运行,* 表示绑定所有接口)
ssh -N -f -R *8899:127.0.0.1:8899 user@server_ip
# 协议还原(监听隧道 TCP 端口,转发至实际 UDP 服务)
socat TCP4-LISTEN:8899,reuseaddr,fork UDP:<Target_IP>:<Target_Port>2. 服务端(协议封装)
在服务端启动 socat,监听本地 UDP 端口并写入隧道 TCP 端口:
# 协议封装(监听 UDP 9999,转发至隧道 TCP 8899)
socat UDP4-LISTEN:9999,reuseaddr,fork TCP:127.0.0.1:8899若希望隧道断线自动重连,建议使用 autossh 替代原生 ssh 命令,例如:autossh -M 0 -N -f -R *8899:127.0.0.1:8899 user@server_ip。
怎么验证是否生效
验证需遵循数据流向:从服务端入口发送,在客户端目标处接收。
1. 目标端监听
在客户端网络内,实际 UDP 服务所在的机器上监听端口(假设目标端口为 53):
nc -u -l -p 532. 服务端发送
在 SSH 服务端,向 Socat 监听的 UDP 端口发送测试数据:
echo "hello_udp" | nc -u 127.0.0.1 99993. 结果确认
若目标端终端显示 hello_udp,则隧道打通。对于 DNS 等业务,可直接将客户端 DNS 服务器地址配置为 server_ip:9999 进行测试。
持久化配置(Systemd)
生产环境不建议手动运行命令,需配置 systemd 服务确保开机自启及崩溃重启。以下以客户端 Socat 为例:
1. 创建服务文件
sudo vi /etc/systemd/system/udp-tunnel.service2. 填写配置内容
[Unit]
Description=SSH UDP Tunnel Socat
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/socat TCP4-LISTEN:8899,reuseaddr,fork UDP:<Target_IP>:<Target_Port>
Restart=always
RestartSec=5
User=root
[Install]
WantedBy=multi-user.target3. 启用服务
执行 sudo systemctl daemon-reload 重载配置,然后 sudo systemctl enable `--now` udp-tunnel.service 启动并设为开机自启。SSH 隧道进程同样建议纳入 systemd 或 supervisord 管理。
故障排查与日志分析
若连通性测试失败,按以下顺序排查:
1. 检查端口监听状态
在服务端和客户端分别执行,确认 socat 和 ssh 端口已监听:
netstat -tulpn | grep -E "8899|9999"
# 或
ss -tulpn | grep -E "8899|9999"2. 检查 SSH 隧道连接
在客户端查看 SSH 进程是否存在,或使用 verbose 模式测试连接:
ssh -v -N -R *8899:127.0.0.1:8899 user@server_ip观察输出中是否有 Listening on port 8899 字样。若报错 Permission denied,通常是因为服务端未开启 GatewayPorts。
3. 检查防火墙
确认服务端防火墙未拦截 UDP 9999 端口,客户端防火墙未拦截 TCP 8899 出站连接。
常见坑
1. 状态丢失与延迟
UDP 是无状态协议,封装进 TCP 隧道后,会继承 TCP 的重传机制。若网络波动,TCP 重传可能导致 UDP 业务延迟增加,不适合对实时性要求极高的场景(如竞技游戏)。
2. SSH3 兼容性风险
SSH3 协议虽原生支持 UDP,但基于 HTTP/3 实现,与标准 OpenSSH 不兼容。生产环境若两端无法统一部署 SSH3 软件,仍需使用 socat 方案。
3. 端口占用冲突
socat 监听端口时若未加 reuseaddr 参数,进程异常退出后端口可能处于 TIME_WAIT 状态导致无法立即重启。务必在命令中带上该参数。
4. 绑定地址限制
SSH -R 默认绑定 localhost。若服务端 Socat 尝试连接非本地地址或外部用户需访问隧道端口,必须在服务端 sshd_config 中设置 GatewayPorts yes,否则连接会被拒绝。