一、基础概念:为什么需要端口转发

端口转发(Port Forwarding)的本质是将对某个本地或远程端口的 TCP/UDP 连接,透明地重定向到另一个地址和端口。常见使用场景包括:
- 内网服务暴露:将内网数据库或 Web 服务安全地暴露给开发机或外部测试环境。
- 穿越NAT:利用 SSH 隧道访问受限资源,无需修改网络设备配置。
- 本地调试代理:在本地端口拦截流量,进行抓包、Mock 或协议分析。
- 多跳访问:通过跳板机链式访问深层内网服务。
二、Windows:netsh interface portproxy
2.1 原理与权限
netsh interface portproxy 是 Windows 内置的 IPv4/IPv6 端口代理工具,基于内核态 TCP 代理实现,无需第三方软件,适合长期驻留。
注意:所有 netsh portproxy 命令必须在管理员权限的 PowerShell 或 CMD 中执行。
2.2 添加转发规则
将本机 0.0.0.0:8080 的流量转发到内网机器 192.168.1.100 的 80 端口:
powershell
netsh interface portproxy add v4tov4 `
listenaddress=0.0.0.0 `
listenport=8080 `
connectaddress=192.168.1.100 `
connectport=80
参数说明:
| 参数 | 说明 |
|---|---|
v4tov4 |
IPv4 → IPv4(另有 v4tov6、v6tov4、v6tov6) |
listenaddress |
监听地址(0.0.0.0 表示所有网卡) |
listenport |
本机监听端口 |
connectaddress |
目标地址 |
connectport |
目标端口 |
2.3 查看所有规则
powershell
netsh interface portproxy show all
输出示例:
less
Listen on ipv4: Connect to ipv4:
Address Port Address Port
--------------- ---------- --------------- ----------
0.0.0.0 8080 192.168.1.100 80
2.4 删除规则
powershell
netsh interface portproxy delete v4tov4 `
listenaddress=0.0.0.0 `
listenport=8080
2.5 清空所有规则
powershell
netsh interface portproxy reset
2.6 防火墙放行(常被遗漏的步骤)
netsh portproxy 本身只创建代理规则,Windows 防火墙仍会默认拦截入站流量,需单独放行:
powershell
# 放行 8080 入站
New-NetFirewallRule `
-DisplayName "PortProxy 8080" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8080 `
-Action Allow
# 不再需要时删除
Remove-NetFirewallRule -DisplayName "PortProxy 8080"
2.7 持久化与服务管理
netsh portproxy 规则在系统重启后默认保留 (写入注册表 HKLM\SYSTEM\CurrentControlSet\Services\PortProxy),无需额外配置即可开机生效。
若需临时关闭全部转发,可禁用 IP Helper 服务(需谨慎,该服务影响其他 IPv6/Teredo 功能):
powershell
# 查看服务状态
Get-Service iphlpsvc
# 停止(临时)
Stop-Service iphlpsvc
2.8 实战场景:Windows 开发机访问 WSL2 内的服务
WSL2 运行在独立的虚拟网络(每次启动 IP 可能变化),以下脚本自动获取 WSL2 的当前 IP 并刷新转发规则:
powershell
# 获取 WSL2 IP(脱密:替换实际接口名称)
$wsl2Ip = (wsl -- hostname -I).Trim().Split(" ")[0]
netsh interface portproxy delete v4tov4 `
listenaddress=127.0.0.1 listenport=3000 2>$null
netsh interface portproxy add v4tov4 `
listenaddress=127.0.0.1 `
listenport=3000 `
connectaddress=$wsl2Ip `
connectport=3000
Write-Host "已将 127.0.0.1:3000 转发至 WSL2 $wsl2Ip:3000"
2.9 gui管理
命令行对新手不够友好, 可以使用下面的工具 zmjack/PortProxyGUI: A manager of netsh interface portproxy which is to evaluate TCP/IP port redirect on windows.
三、SSH 端口转发
SSH 的 -N(不执行远程命令)和 -f(后台运行)参数在所有隧道场景中几乎都应成对使用:
bash
ssh -N -f [转发参数] user@ssh-server
以下示例中,foo、bar、example.com、192.168.x.x 均为脱密占位符,请替换为实际值。
3.1 本地转发(Local Forward,-L)
语法:
bash
ssh -L [本地绑定地址:]本地端口:目标主机:目标端口 user@ssh跳板机
典型用法:通过跳板机访问内网数据库
bash
# 将本地 5432 端口转发到内网数据库
ssh -N -f \
-L 127.0.0.1:5432:db.internal.example.com:5432 \
foo@jump.example.com
建立后,在本地直接连接 127.0.0.1:5432 即可访问内网数据库,跳板机作为透明中继。
允许局域网内其他主机访问(绑定到 0.0.0.0):
bash
ssh -N -f \
-L 0.0.0.0:8080:web.internal.example.com:80 \
foo@jump.example.com
安全提示: 绑定
0.0.0.0会暴露给本地网络上的所有设备,生产环境建议仅绑定127.0.0.1。
3.2 远程转发(Remote Forward,-R)
语法:
bash
ssh -R [远程绑定地址:]远程端口:本地目标主机:本地目标端口 user@ssh服务器
典型用法:将本地开发服务暴露给公网服务器
bash
# 让公网服务器的 9090 端口指向本地的 3000 端口
ssh -N -f \
-R 0.0.0.0:9090:127.0.0.1:3000 \
foo@pub.example.com
此后,任何访问 pub.example.com:9090 的流量都会被反向引导到你的本地 3000 端口。
服务器配置要求:
远程转发绑定非 localhost 地址时,需在 SSH 服务端(/etc/ssh/sshd_config)开启:
bash
GatewayPorts yes
修改后重启 sshd:
bash
sudo systemctl restart sshd
3.3 动态转发(Dynamic Forward,-D)
动态转发在本地创建一个 SOCKS5 代理,所有发往该代理的流量均通过 SSH 服务器出口,无需事先指定目标地址,等同于一个轻量级 VPN。
语法:
bash
ssh -D [绑定地址:]本地SOCKS端口 user@ssh服务器
示例:
bash
ssh -N -f -D 127.0.0.1:1080 foo@jump.example.com
配合 curl 使用:
bash
curl --socks5-hostname 127.0.0.1:1080 http://internal.example.com
配合 Git 使用:
bash
git config --global http.proxy socks5h://127.0.0.1:1080
配合 proxychains(Linux)使用:
编辑 /etc/proxychains.conf,在末尾添加:
yaml
socks5 127.0.0.1 1080
然后:
bash
proxychains curl http://internal.example.com
3.4 多跳转发:ProxyJump(-J)
当目标服务器无法直接访问,需要经过一或多个跳板机时,使用 -J 参数(SSH 7.3+):
单跳:
bash
ssh -J foo@jump.example.com bar@target.internal.example.com
多跳(逗号分隔):
bash
ssh -J foo@jump1.example.com,foo@jump2.example.com bar@target.internal.example.com
结合本地转发:
bash
ssh -J foo@jump.example.com \
-L 127.0.0.1:5432:db.internal.example.com:5432 \
bar@bastion.internal.example.com -N -f
写入 ~/.ssh/config(推荐,长期使用):
javascript
Host jump
HostName jump.example.com
User foo
IdentityFile ~/.ssh/id_rsa_jump
Host target
HostName target.internal.example.com
User bar
ProxyJump jump
IdentityFile ~/.ssh/id_rsa_target
Host db-tunnel
HostName bastion.internal.example.com
User bar
ProxyJump jump
LocalForward 5432 db.internal.example.com:5432
之后只需:
bash
ssh target # 直接连接目标
ssh -N -f db-tunnel # 建立数据库隧道
3.5 平台差异速查
| 功能 | Linux | macOS | Windows(内置 OpenSSH) |
|---|---|---|---|
-L 本地转发 |
✅ | ✅ | ✅(Win10 1809+) |
-R 远程转发 |
✅ | ✅ | ✅ |
-D 动态转发 |
✅ | ✅ | ✅ |
-J ProxyJump |
✅ SSH 7.3+ | ✅ SSH 7.3+ | ✅ OpenSSH 8.0+ |
| 查看 SSH 版本 | ssh -V |
ssh -V |
ssh -V |
| SSH 客户端路径 | /usr/bin/ssh |
/usr/bin/ssh |
C:\Windows\System32\OpenSSH\ssh.exe |
| 后台运行 | -f |
-f |
需 Task Scheduler 或 Start-Process |
Windows 安装/启用内置 OpenSSH:
powershell
# 检查是否已安装
Get-WindowsCapability -Online -Name OpenSSH.Client*
# 安装
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Windows 后台运行 SSH 隧道(PowerShell):
powershell
Start-Process ssh -ArgumentList "-N -L 127.0.0.1:5432:db.internal.example.com:5432 foo@jump.example.com" -WindowStyle Hidden
四、隧道管理与常见问题
4.1 查找并终止隧道进程
Linux / macOS:
bash
# 查找占用端口的进程
lsof -i :5432
ss -tlnp | grep 5432 # Linux
# 按名称杀掉后台 ssh
pkill -f "ssh.*5432"
Windows:
powershell
# 查找端口占用
netstat -ano | findstr :8080
# 根据 PID 终止
Stop-Process -Id <PID>
4.2 保持连接活跃(ServerAliveInterval)
长时间不活动时 SSH 隧道容易断开,在 ~/.ssh/config 中添加:
markdown
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
或在命令行中:
bash
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -N -f \
-L 127.0.0.1:5432:db.internal.example.com:5432 foo@jump.example.com
4.3 使用 autossh 自动重连(Linux/macOS)
bash
# 安装
sudo apt install autossh # Debian/Ubuntu
brew install autossh # macOS
# 使用(-M 0 禁用监控端口,依赖 ServerAlive* 心跳)
autossh -M 0 -N -f \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-L 127.0.0.1:5432:db.internal.example.com:5432 \
foo@jump.example.com
4.4 常见错误排查
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
bind: Address already in use |
本地端口被占用 | 更换端口或杀掉占用进程 |
channel 3: open failed: connect failed |
SSH 服务器无法到达目标 | 检查目标地址/防火墙 |
remote port forwarding failed for listen port |
远程端口被占用或 GatewayPorts 未开启 | 更换端口或修改 sshd_config |
Permission denied (publickey) |
密钥未配置 | 检查 ~/.ssh/authorized_keys |
| netsh 规则添加成功但无法访问 | Windows 防火墙未放行 | 添加入站防火墙规则 |
五、综合实战:开发环境完整配置示例
以下是一个将多种技术综合使用的典型开发场景:本地 Windows 机器,通过 SSH 隧道访问远端 Kubernetes 集群内的多个服务。
目标:
localhost:8080→ 远端 Nginx(通过 SSH -L)localhost:5432→ 远端 PostgreSQL(通过 SSH -L + ProxyJump)localhost:1080→ SOCKS5 动态代理(通过 SSH -D)
~/.ssh/config 配置(Windows 路径 %USERPROFILE%\.ssh\config):
javascript
Host bastion
HostName jump.example.com
User foo
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
ServerAliveCountMax 3
Host k8s-tunnels
HostName 10.0.0.1
User bar
ProxyJump bastion
LocalForward 127.0.0.1:8080 nginx.cluster.local:80
LocalForward 127.0.0.1:5432 postgres.cluster.local:5432
DynamicForward 127.0.0.1:1080
一条命令建立全部隧道:
bash
ssh -N -f k8s-tunnels
至此,本地即可:
bash
curl http://localhost:8080 # 访问 Nginx
psql -h localhost -p 5432 -U bar # 连接 PostgreSQL
curl --socks5-hostname localhost:1080 http://internal.svc # 动态代理访问任意内网服务
总结
| 场景 | 工具 | 关键参数/命令 |
|---|---|---|
| Windows 持久端口映射 | netsh portproxy | add v4tov4 listenport=X connectaddress=Y connectport=Z |
| 访问远端内网服务 | SSH 本地转发 | -L 本地端口:目标:目标端口 |
| 将本地服务暴露到公网 | SSH 远程转发 | -R 远程端口:本地:本地端口 |
| 全流量代理穿越防火墙 | SSH 动态转发 | -D 本地SOCKS端口 |
| 多跳跳板机访问 | ProxyJump | -J 跳板机 或 config 中 ProxyJump |
| 隧道自动重连 | autossh | autossh -M 0 -N -f ... |
端口转发的核心思路始终是:找到可信的中继节点,利用已允许的通道承载受限流量。掌握以上工具后,大多数网络访问限制问题都可以得到安全、优雅的解决。