WSL2 NAT 端口转发管理工具 使用说明
1. 工具简介
本工具是一个 PowerShell 脚本,用于在 WSL2 NAT 网络模式 下管理 Windows 到 WSL2 的端口转发规则。它可以帮助您:
- 查看当前已配置的 TCP 端口转发规则和 UDP 防火墙规则
- 添加新的 TCP 端口转发(自动配置 Windows 防火墙)
- 添加 UDP 端口防火墙规则(提示 netsh 不支持 UDP 转发)
- 删除不再需要的 TCP/UDP 规则(基于序号操作)
注意:由于 Windows 的
netsh interface portproxy命令不支持 UDP 转发,本工具对于 UDP 协议仅能添加/删除 Windows 防火墙入站规则,实际的 UDP 端口转发需要采用其他方案(如 WSL2 镜像网络模式或在 WSL2 内部使用 socat)。
2. 使用前提
- 操作系统:Windows 10/11 且已安装 WSL2
- 权限:需要以管理员身份运行脚本(脚本会自动请求提权)
- PowerShell:建议使用 PowerShell 5.1 或更高版本
- WSL2:目标 WSL2 发行版必须处于运行状态(可通过
wsl命令启动)
3. 安装与首次运行
3.1 下载脚本
将以下脚本内容保存为 wsl_nat_port_manager.ps1 文件(推荐放在一个单独的文件夹,例如 C:\Users\你的用户名\Documents\WSL_tools\):
# 此处粘贴完整脚本代码(见前文最终版)
3.2 首次运行
由于 Windows 默认禁止运行 PowerShell 脚本,您可以采用以下任一方式运行:
方法一:使用命令行绕过执行策略(推荐)
- 以管理员身份打开 PowerShell
- 切换到脚本所在目录,执行:
powershell -ExecutionPolicy Bypass -File .\wsl_nat_port_manager.ps1
方法二:创建批处理启动器
- 在脚本同目录下新建一个文本文件,命名为
启动端口管理.bat - 写入以下内容:
@echo off powershell -ExecutionPolicy Bypass -File "%~dp0wsl_nat_port_manager.ps1" pause - 以后右键该
.bat文件 → 以管理员身份运行
方法三:永久修改执行策略(仅限个人开发机)
- 以管理员身份打开 PowerShell,执行:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser - 输入
Y确认。之后可以直接双击.ps1文件运行。
4. 界面与操作指南
运行脚本后,会显示主菜单:
======================================
WSL2 NAT 端口转发管理工具
======================================
当前 WSL2 IP: 172.18.87.79
已转发的 TCP 端口:
1996 (BT-Panel) 3390 (xrdp) 9659 (自定义)
已添加的 UDP 端口 (仅防火墙):
无
请选择操作:
1. 查看详细规则
2. 添加端口 (TCP/UDP)
3. 删除端口 (输入序号)
0. 退出
4.1 查看详细规则 (选项 1)
显示所有 TCP 转发规则(包含目标 IP 和注释)以及 UDP 防火墙规则列表。
4.2 添加端口 (选项 2)
- 输入要添加的端口号(支持多个,用英文逗号分隔,例如
1996,3390,8080)。 - 选择协议:
1= TCP,2= UDP。- 若选择 UDP,工具会提示
netsh不支持 UDP 转发,询问是否仅添加防火墙规则。选择y后,会创建 Windows 防火墙入站规则(允许 UDP),但不会执行端口转发。
- 若选择 UDP,工具会提示
- 确认后,工具会自动:
- 删除可能存在的旧 TCP 转发规则(避免冲突)
- 添加新的 TCP 转发规则(指向当前 WSL2 IP)
- 创建/确认防火墙规则(TCP/UDP 分别命名:
WSL TCP Port xxxx/WSL UDP Port xxxx)
4.3 删除端口 (选项 3)
- 工具会列出所有已存在的规则(TCP 转发 + UDP 防火墙),并统一编号。
- 输入要删除的序号(例如
1、2,3、1-3等)。 - 确认后,工具会:
- 删除 TCP 端口转发规则(执行
netsh interface portproxy delete) - 删除 UDP 防火墙规则(执行
Remove-NetFirewallRule)
- 删除 TCP 端口转发规则(执行
提示:删除操作不会自动删除对应的防火墙规则(TCP 防火墙规则会保留,因为可能被其他程序使用)。如需彻底清理,可手动运行
Remove-NetFirewallRule -DisplayName "WSL TCP Port xxxx"。
4.4 退出 (选项 0)
退出脚本。
5. 注意事项
-
WSL2 IP 会变化
每次重启 WSL2 或 Windows 后,WSL2 的虚拟 IP 地址可能会改变。建议每次 WSL2 重启后重新运行本工具,更新端口转发规则(添加时会自动使用最新 IP)。 -
UDP 转发的限制
Windows 的netsh interface portproxy不支持 UDP。如果您的应用确实需要 UDP 转发,请考虑以下替代方案:- 方案一(推荐):使用 WSL2 的镜像网络模式。编辑
%UserProfile%\.wslconfig,设置networkingMode=Mirrored,然后执行wsl --shutdown重启 WSL。之后 WSL 与主机共享网络,UDP 端口自动可用。 - 方案二:在 WSL2 内部安装
socat,手动进行 UDP 转发。例如:sudo apt install socat socat UDP-LISTEN:1996,fork UDP:172.18.87.79:1996(将
172.18.87.79替换为你的 WSL2 IP)
- 方案一(推荐):使用 WSL2 的镜像网络模式。编辑
-
防火墙规则清理
本工具添加的防火墙规则名称格式为WSL TCP Port xxxx和WSL UDP Port xxxx。如果需要手动删除,可以在管理员 PowerShell 中执行:Remove-NetFirewallRule -DisplayName "WSL TCP Port 1996" Remove-NetFirewallRule -DisplayName "WSL UDP Port 1996" -
脚本自动提权
脚本开头包含了自动请求管理员权限的代码。如果您双击运行,会弹出 UAC 提示,请点击“是”。 -
WSL2 未启动时的处理
如果 WSL2 未运行,脚本会提示“无法获取 WSL IP”并退出。请先启动 WSL(例如在终端中执行wsl或打开一个 WSL 窗口)。
6. 常见问题 (FAQ)
Q1:运行脚本时提示“无法加载文件,因为在此系统上禁止运行脚本”
A:请参考第 3.2 节“首次运行”中的方法,使用 -ExecutionPolicy Bypass 参数运行,或修改执行策略。
Q2:添加 TCP 端口后,从 Windows 访问 localhost:端口 仍然失败
A:请按以下步骤排查:
- 确认 WSL2 内部的服务确实在监听
0.0.0.0而不是127.0.0.1(在 WSL2 中运行ss -tlnp | grep 端口检查)。 - 检查 Windows 防火墙是否允许该端口(脚本会自动添加规则,但某些安全软件可能阻止)。
- 确认 WSL2 IP 是否发生变化(运行
wsl hostname -I获取最新 IP,并与转发规则中的 IP 比对)。
Q3:删除端口后,端口仍然可以访问?
A:可能是防火墙规则未删除。脚本目前只删除 TCP 转发规则和 UDP 防火墙规则,但保留了 TCP 防火墙规则(避免误删)。您可以手动删除对应的防火墙规则(见第 5 节)。
Q4:如何批量添加端口?
A:在添加端口时,输入用逗号分隔的端口号即可,例如 1996,3390,8080。
Q5:为什么 UDP 端口不能转发?
A:这是 Windows netsh portproxy 的限制。建议改用 WSL2 镜像网络模式(见第 5 节)。
7. 卸载
本工具是一个独立的 PowerShell 脚本,不会在系统中安装任何文件。如需“卸载”,直接删除脚本文件即可。防火墙规则和端口转发规则可通过脚本的删除功能清除,也可手动清理(参见第 5 节)。
8. 更新日志
- v1.0(2026-04-07)
- 初始版本
- 支持 TCP 端口转发添加/删除/查看
- 支持 UDP 防火墙规则添加/删除
- 自动获取 WSL2 IP 并配置转发
- 交互式菜单,实时显示当前端口
如有任何问题或建议,欢迎反馈。
源码
# ============================================
# WSL2 NAT 端口转发管理工具 (支持 TCP/UDP 完整管理)
# 功能:查看/添加/删除 TCP 转发 + UDP 防火墙规则
# ============================================
# ---------- 自动请求管理员权限 ----------
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$arguments = "-ExecutionPolicy Bypass -File `"" + $MyInvocation.MyCommand.Path + "`""
Start-Process powershell -Verb RunAs -ArgumentList $arguments
exit
}
# ---------- 常用端口注释 ----------
$portComments = @{
22 = "SSH"
80 = "HTTP"
443 = "HTTPS"
3306 = "MySQL"
5432 = "PostgreSQL"
6379 = "Redis"
27017 = "MongoDB"
8080 = "Web备用"
8888 = "Jupyter"
3389 = "RDP"
5900 = "VNC"
}
# ---------- 获取 WSL IP ----------
function Get-WslIp {
$ip = (wsl hostname -I).Trim()
if (-not $ip) {
Write-Error "无法获取 WSL IP,请确保 WSL 正在运行"
pause
exit 1
}
return $ip
}
# ---------- 获取当前 TCP 转发规则(端口 -> 目标IP)----------
function Get-TcpRules {
$output = netsh interface portproxy show v4tov4 2>$null
$rules = @{}
if ([string]::IsNullOrWhiteSpace($output)) { return $rules }
$lines = $output -split "`r`n"
foreach ($line in $lines) {
if ($line -match '0\.0\.0\.0\s+(\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+\d+') {
$port = [int]$Matches[1]
$target = $Matches[2]
$rules[$port] = $target
}
}
return $rules
}
# ---------- 获取所有已添加的 UDP 防火墙规则(端口列表)----------
function Get-UdpFirewallPorts {
$rules = Get-NetFirewallRule -DisplayName "WSL UDP Port *" -ErrorAction SilentlyContinue
$ports = @()
foreach ($rule in $rules) {
# 从规则名称中提取端口号,例如 "WSL UDP Port 1996"
if ($rule.DisplayName -match 'WSL UDP Port (\d+)') {
$ports += [int]$Matches[1]
}
}
return ($ports | Sort-Object -Unique)
}
# ---------- 显示当前 TCP 端口列表(主菜单用)----------
function Show-TcpPortList {
$rules = Get-TcpRules
if ($rules.Count -eq 0) {
Write-Host " (无)" -ForegroundColor DarkGray
return
}
$sorted = $rules.Keys | Sort-Object
$output = @()
foreach ($port in $sorted) {
$comment = if ($portComments.ContainsKey($port)) { "($($portComments[$port]))" } else { "" }
$output += "$port $comment"
}
Write-Host (" " + ($output -join " ")) -ForegroundColor Green
}
# ---------- 显示当前 UDP 防火墙端口列表(主菜单用)----------
function Show-UdpPortList {
$udpPorts = Get-UdpFirewallPorts
if ($udpPorts.Count -eq 0) {
Write-Host " (无)" -ForegroundColor DarkGray
return
}
$output = @()
foreach ($port in $udpPorts) {
$comment = if ($portComments.ContainsKey($port)) { "($($portComments[$port]))" } else { "" }
$output += "$port $comment"
}
Write-Host (" " + ($output -join " ")) -ForegroundColor Green
}
# ---------- 显示详细规则表格(TCP)----------
function Show-DetailRules {
$rules = Get-TcpRules
if ($rules.Count -eq 0) {
Write-Host "`n当前没有任何 TCP 端口转发规则。" -ForegroundColor Yellow
} else {
Write-Host "`n当前 TCP 端口转发规则:" -ForegroundColor Cyan
Write-Host ("{0,-8} {1,-20} {2,-15}" -f "端口", "目标地址", "注释")
Write-Host ("{0,-8} {1,-20} {2,-15}" -f "----", "--------", "----")
foreach ($port in ($rules.Keys | Sort-Object)) {
$target = $rules[$port]
$comment = if ($portComments.ContainsKey($port)) { $portComments[$port] } else { "" }
Write-Host ("{0,-8} {1,-20} {2,-15}" -f $port, $target, $comment)
}
}
$udpPorts = Get-UdpFirewallPorts
if ($udpPorts.Count -eq 0) {
Write-Host "`n当前没有任何 UDP 防火墙规则。" -ForegroundColor Yellow
} else {
Write-Host "`n当前 UDP 防火墙规则(仅入站允许,无转发):" -ForegroundColor Cyan
foreach ($port in $udpPorts) {
$comment = if ($portComments.ContainsKey($port)) { "($($portComments[$port]))" } else { "" }
Write-Host " $port $comment" -ForegroundColor Green
}
}
Write-Host ""
}
# ---------- 添加端口(TCP/UDP)----------
function Add-Ports {
param($wslIp)
# 显示当前已有端口
$existingTcp = Get-TcpRules
$existingUdp = Get-UdpFirewallPorts
Write-Host "`n当前已转发的 TCP 端口: " -ForegroundColor Cyan -NoNewline
if ($existingTcp.Count -gt 0) { Write-Host ($existingTcp.Keys | Sort-Object) -ForegroundColor Green } else { Write-Host "无" -ForegroundColor DarkGray }
Write-Host "当前 UDP 防火墙端口: " -ForegroundColor Cyan -NoNewline
if ($existingUdp.Count -gt 0) { Write-Host $existingUdp -ForegroundColor Green } else { Write-Host "无" -ForegroundColor DarkGray }
Write-Host "`n请输入要添加的端口号(多个用逗号分隔,例如 1996,3390,8080):" -ForegroundColor Cyan
$input = Read-Host "端口列表"
if ([string]::IsNullOrWhiteSpace($input)) {
Write-Host "未输入端口,返回主菜单。" -ForegroundColor Yellow
return
}
$ports = $input -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ }
if ($ports.Count -eq 0) {
Write-Host "没有有效的端口号。" -ForegroundColor Red
return
}
# 选择协议
Write-Host "请选择协议: 1 = TCP, 2 = UDP" -ForegroundColor Cyan
$protoChoice = Read-Host "请输入数字"
$protocol = if ($protoChoice -eq '1') { "TCP" } elseif ($protoChoice -eq '2') { "UDP" } else { "TCP" }
if ($protocol -eq "UDP") {
Write-Host "⚠ netsh portproxy 不支持 UDP 转发。" -ForegroundColor Red
Write-Host " 将仅为这些端口添加 Windows 防火墙规则(允许入站 UDP)。" -ForegroundColor Yellow
Write-Host " 实际的 UDP 端口转发需要在 WSL2 内自行配置,例如:" -ForegroundColor Gray
Write-Host " - 使用镜像网络模式 (networkingMode=Mirrored)" -ForegroundColor Gray
Write-Host " - 在 WSL2 内运行 socat UDP-LISTEN:端口,fork UDP:目标IP:端口" -ForegroundColor Gray
$continue = Read-Host "是否继续添加 UDP 防火墙规则?(y/N)"
if ($continue -ne 'y' -and $continue -ne 'Y') { return }
}
# 确认添加
Write-Host "将添加端口: $($ports -join ', ') 协议: $protocol" -ForegroundColor Yellow
$confirm = Read-Host "确认操作?(Y/N)"
if ($confirm -ne 'Y' -and $confirm -ne 'y') { return }
foreach ($port in $ports) {
if ($protocol -eq "TCP") {
# 删除旧规则(避免冲突)
netsh interface portproxy delete v4tov4 listenport=$port listenaddress=0.0.0.0 2>$null
# 添加新规则
netsh interface portproxy add v4tov4 listenport=$port listenaddress=0.0.0.0 connectport=$port connectaddress=$wslIp 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "✓ TCP 端口 $port 已转发至 $wslIp" -ForegroundColor Green
} else {
Write-Host "✗ TCP 端口 $port 转发失败" -ForegroundColor Red
}
}
# 防火墙规则(TCP 和 UDP 都添加)
$ruleName = "WSL $protocol Port $port"
$existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
if (-not $existingRule) {
New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol $protocol -LocalPort $port | Out-Null
Write-Host "✓ 防火墙规则 ($protocol) $ruleName 已创建" -ForegroundColor Green
} else {
Write-Host "✓ 防火墙规则 ($protocol) $ruleName 已存在" -ForegroundColor Gray
}
}
Write-Host "添加完成。" -ForegroundColor Green
pause "按 Enter 继续..."
}
# ---------- 删除端口(支持 TCP 规则和 UDP 防火墙规则)----------
function Remove-Ports {
$tcpRules = Get-TcpRules
$udpPorts = Get-UdpFirewallPorts
if ($tcpRules.Count -eq 0 -and $udpPorts.Count -eq 0) {
Write-Host "没有可删除的规则。" -ForegroundColor Yellow
pause
return
}
# 显示 TCP 规则
if ($tcpRules.Count -gt 0) {
Write-Host "`n当前 TCP 端口转发规则:" -ForegroundColor Cyan
$tcpList = @($tcpRules.Keys | Sort-Object)
for ($i = 0; $i -lt $tcpList.Count; $i++) {
$port = $tcpList[$i]
$comment = if ($portComments.ContainsKey($port)) { " ($($portComments[$port]))" } else { "" }
Write-Host "$($i+1). TCP $port$comment"
}
}
# 显示 UDP 规则
if ($udpPorts.Count -gt 0) {
Write-Host "`n当前 UDP 防火墙规则 (仅允许入站,无转发):" -ForegroundColor Cyan
$udpList = $udpPorts | Sort-Object
$startIdx = $tcpRules.Count
for ($i = 0; $i -lt $udpList.Count; $i++) {
$port = $udpList[$i]
$comment = if ($portComments.ContainsKey($port)) { " ($($portComments[$port]))" } else { "" }
Write-Host "$($startIdx + $i + 1). UDP $port$comment"
}
}
Write-Host "`n请输入要删除的规则序号(例如 1,3)或直接输入端口号(例如 1996,3390):" -ForegroundColor Cyan
$input = Read-Host "选择"
if ([string]::IsNullOrWhiteSpace($input)) { return }
$toDeleteTcp = @()
$toDeleteUdp = @()
$items = $input -split ','
foreach ($item in $items) {
$item = $item.Trim()
if ($item -match '^\d+$') {
$num = [int]$item
if ($num -le $tcpRules.Count) {
$port = ($tcpRules.Keys | Sort-Object)[$num-1]
$toDeleteTcp += $port
} elseif ($num -le ($tcpRules.Count + $udpPorts.Count)) {
$udpIndex = $num - $tcpRules.Count - 1
if ($udpIndex -ge 0 -and $udpIndex -lt $udpPorts.Count) {
$port = ($udpPorts | Sort-Object)[$udpIndex]
$toDeleteUdp += $port
} else {
Write-Host "无效序号: $num" -ForegroundColor Red
}
} else {
Write-Host "无效序号: $num" -ForegroundColor Red
}
} elseif ($item -match '^\d+$') { # 直接端口号
$port = [int]$item
if ($tcpRules.ContainsKey($port)) {
$toDeleteTcp += $port
} elseif ($udpPorts -contains $port) {
$toDeleteUdp += $port
} else {
Write-Host "端口 $port 未找到任何规则。" -ForegroundColor Red
}
} else {
Write-Host "无效输入: $item" -ForegroundColor Red
}
}
if ($toDeleteTcp.Count -eq 0 -and $toDeleteUdp.Count -eq 0) { return }
Write-Host "将删除 TCP 端口: $($toDeleteTcp -join ', ')" -ForegroundColor Yellow
Write-Host "将删除 UDP 端口: $($toDeleteUdp -join ', ')" -ForegroundColor Yellow
$confirm = Read-Host "确认删除?(Y/N)"
if ($confirm -ne 'Y' -and $confirm -ne 'y') { return }
foreach ($port in $toDeleteTcp) {
netsh interface portproxy delete v4tov4 listenport=$port listenaddress=0.0.0.0
Write-Host "✓ 已删除 TCP 端口 $port 的转发规则" -ForegroundColor Green
# 可选:同时删除对应的防火墙规则(但保留以免影响其他)
# Remove-NetFirewallRule -DisplayName "WSL TCP Port $port" -ErrorAction SilentlyContinue
}
foreach ($port in $toDeleteUdp) {
Remove-NetFirewallRule -DisplayName "WSL UDP Port $port" -ErrorAction SilentlyContinue
Write-Host "✓ 已删除 UDP 端口 $port 的防火墙规则" -ForegroundColor Green
}
Write-Host "删除完成。" -ForegroundColor Green
pause "按 Enter 继续..."
}
# ---------- 主菜单(实时显示端口列表)----------
function MainMenu {
Clear-Host
$wslIp = Get-WslIp
Write-Host "======================================" -ForegroundColor Cyan
Write-Host " WSL2 NAT 端口转发管理工具" -ForegroundColor White
Write-Host "======================================" -ForegroundColor Cyan
Write-Host "当前 WSL2 IP 地址: $wslIp" -ForegroundColor Green
Write-Host ""
Write-Host "已转发的 TCP 端口 (转发规则):" -ForegroundColor Yellow
Show-TcpPortList
Write-Host ""
Write-Host "已添加的 UDP 端口 (仅防火墙):" -ForegroundColor Yellow
Show-UdpPortList
Write-Host ""
Write-Host "请选择操作:" -ForegroundColor Yellow
Write-Host " 1. 查看详细规则 (TCP + UDP)"
Write-Host " 2. 添加端口转发 (TCP/UDP)"
Write-Host " 3. 删除端口转发/防火墙规则 (TCP/UDP)"
Write-Host " 0. 退出"
Write-Host ""
$choice = Read-Host "请输入数字"
switch ($choice) {
'1' { Show-DetailRules; pause "按 Enter 返回菜单..."; MainMenu }
'2' { Add-Ports -wslIp $wslIp; MainMenu }
'3' { Remove-Ports; MainMenu }
'0' { Write-Host "退出脚本。"; exit 0 }
default { Write-Host "无效选项"; pause "按 Enter 返回菜单..."; MainMenu }
}
}
# ---------- 入口 ----------
MainMenu


