每台 Windows 10 工控机作为 Tailscale 端口转发网关,用系统原生 netsh portproxy 做端口映射, 配合 任务计划程序实现开机重建与定时巡检,做到长期运行、断网自愈、零人工干预。各仓库内网网段可完全相同,路由器品牌无要求。
每台 Win10 工控机作为 Tailscale 端口转发网关,使用系统原生 netsh portproxy 做端口映射, 通过 任务计划程序实现开机重建与定时巡检的持久化保活,配合 PowerShell 健康检查脚本自动恢复丢失的转发规则。
| 对比项 | 子网路由方案 | 端口转发方案(本方案采用) |
|---|---|---|
| 网段要求 | 各仓库网段必须不同 | 各仓库网段可以完全相同 |
| 路由器依赖 | 需支持静态路由或允许 ICMP | 无要求 任意路由器均可 |
| ACL 粒度 | 按网段控制 | 按 端口 + 设备 精确控制 |
| 配置复杂度 | 需批准路由、开启 accept-routes | 即装即用 客户端无需额外配置 |
| 扩展性 | 新增设备需改路由宣告 | 改配置文件即可 |
多仓库、多品牌路由器、网段可能冲突的场景下,端口转发更稳定可控 —— 这是本方案的唯一选择。
| 工控机监听端口 | 映射目标 | 用途 | 协议 |
|---|---|---|---|
| 8000 | NVR 主机的 80 | NVR Web 管理界面 | HTTP |
| 8554 | NVR 主机的 554 | RTSP 视频流 | RTSP |
| 8001 | NVR2 的 80 | 第二台 NVR Web(如有) | HTTP |
| 8555 | NVR2 的 554 | 第二台 NVR RTSP(如有) | RTSP |
访问方式统一为 http://<工控机TS-IP>:8000
本方案最终实现未使用 NSSM,而是用 Windows 任务计划程序完成「开机重建 + 定时巡检」—— 效果等价、零额外依赖、无需注册服务。NSSM 仅作为备选方案保留在工具清单中。
Windows 10 专业版 / 企业版 / IoT 企业版。家庭版部分功能受限,但端口转发可用。
Pro / Enterprise / IoTiphlpsvc 必须启用(默认开启),netsh portproxy 依赖此服务。
允许 Tailscale 与转发端口(8000 / 8554 / 8001 / 8555)入站,脚本会自动创建规则。
Inbound TCP Allow#Requires -RunAsAdministrator
# ============================================
# 仓库 Tailscale 网关一键配置脚本(Windows 10 版)
# 功能:安装 Tailscale + 配置 NVR 端口转发 + 持久化 + 保活
# ============================================
param(
[string]$GatewayName = "warehouse-a",
[string]$TailscaleAuthKey = "",
[string]$NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp"
# 格式: "本地端口,NVR内网IP,NVR端口,服务名;..."
)
$ErrorActionPreference = "Stop"
# ---------- 配置区 ----------
$InstallDir = "C:\NVR-Gateway"
$LogDir = "$InstallDir\Logs"
$ConfigFile = "$InstallDir\config.json"
# 解析 NVR 配置
$NvrList = @()
foreach ($item in $NvrConfig -split ";") {
$parts = $item -split ","
$NvrList += [PSCustomObject]@{
LocalPort = [int]$parts[0]
NvrIp = $parts[1]
NvrPort = [int]$parts[2]
SvcName = $parts[3]
}
}
Write-Host "=== [$GatewayName] 网关部署开始 ===" -ForegroundColor Cyan
# 1. 创建目录
Write-Host "[1/8] 创建目录结构..." -ForegroundColor Yellow
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
New-Item -ItemType Directory -Path "$InstallDir\Scripts" -Force | Out-Null
# 2. 保存配置文件(JSON,便于后续修改和读取)
Write-Host "[2/8] 保存配置文件..." -ForegroundColor Yellow
$config = @{
GatewayName = $GatewayName
NvrList = $NvrList
InstallDir = $InstallDir
LogDir = $LogDir
}
$config | ConvertTo-Json -Depth 3 | Out-File -FilePath $ConfigFile -Encoding UTF8
# 3. 检查并安装 Tailscale
Write-Host "[3/8] 检查 Tailscale..." -ForegroundColor Yellow
$tsPath = "${env:ProgramFiles}\Tailscale\tailscale.exe"
if (-not (Test-Path $tsPath)) {
Write-Host "Tailscale 未安装,请手动下载安装后重新运行脚本" -ForegroundColor Red
Write-Host "下载地址: https://tailscale.com/download/windows" -ForegroundColor Yellow
Start-Process "https://tailscale.com/download/windows"
Read-Host "安装完成后按回车继续..."
$tsPath = "${env:ProgramFiles}\Tailscale\tailscale.exe"
}
# 4. 启动/登录 Tailscale
Write-Host "[4/8] 启动 Tailscale..." -ForegroundColor Yellow
if ($TailscaleAuthKey) {
& $tsPath up --authkey="$TailscaleAuthKey" --hostname="$GatewayName" --accept-dns=false --ssh
} else {
Write-Host ">>> 请执行以下命令完成登录(完成后按回车继续):" -ForegroundColor Green
Write-Host " tailscale up --hostname=$GatewayName --accept-dns=false --ssh" -ForegroundColor White
Read-Host
}
Start-Sleep -Seconds 3
$tsIp = & $tsPath ip -4 2>$null
Write-Host ">>> Tailscale IP: $tsIp" -ForegroundColor Green
# 5. 配置 netsh 端口转发
Write-Host "[5/8] 配置端口转发..." -ForegroundColor Yellow
foreach ($nvr in $NvrList) {
$listenPort = $nvr.LocalPort
$targetIp = $nvr.NvrIp
$targetPort = $nvr.NvrPort
$svcName = $nvr.SvcName
# 先删除可能存在的旧规则
netsh interface portproxy delete v4tov4 listenport=$listenPort listenaddress=0.0.0.0 2>$null | Out-Null
# 添加新规则
netsh interface portproxy add v4tov4 listenport=$listenPort listenaddress=0.0.0.0 connectport=$targetPort connectaddress=$targetIp
Write-Host " OK $svcName : 0.0.0.0:$listenPort -> $targetIp`:$targetPort" -ForegroundColor Green
}
# 6. 配置 Windows 防火墙
Write-Host "[6/8] 配置防火墙规则..." -ForegroundColor Yellow
foreach ($nvr in $NvrList) {
$listenPort = $nvr.LocalPort
$ruleName = "NVR-Gateway-Port-$listenPort"
# 删除旧规则
Remove-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
# 添加入站规则(仅允许 Tailscale 虚拟网卡接口,或全部接口)
New-NetFirewallRule `
-DisplayName $ruleName `
-Direction Inbound `
-Protocol TCP `
-LocalPort $listenPort `
-Action Allow `
-Profile Any `
-ErrorAction SilentlyContinue | Out-Null
Write-Host " OK 防火墙规则: $ruleName (端口 $listenPort)" -ForegroundColor Green
}
# 7. 生成健康检查脚本
Write-Host "[7/8] 生成健康检查脚本..." -ForegroundColor Yellow
$healthCheckScript = @'
param([string]$ConfigFile = "C:\NVR-Gateway\config.json")
$cfg = Get-Content $ConfigFile | ConvertFrom-Json
$logFile = "$($cfg.LogDir)\health-check.log"
function Write-Log($msg) {
$line = "[{0:yyyy-MM-dd HH:mm:ss}] {1}" -f (Get-Date), $msg
Add-Content -Path $logFile -Value $line
Write-Host $line
}
foreach ($nvr in $cfg.NvrList) {
$tcp = New-Object System.Net.Sockets.TcpClient
try {
$tcp.Connect($nvr.NvrIp, $nvr.NvrPort)
$tcp.Close()
Write-Log ("OK {0} ({1}:{2}) 健康" -f $nvr.SvcName, $nvr.NvrIp, $nvr.NvrPort)
}
catch {
Write-Log ("NG {0} ({1}:{2}) 不可达,正在重建端口转发..." -f $nvr.SvcName, $nvr.NvrIp, $nvr.NvrPort)
netsh interface portproxy delete v4tov4 listenport=$($nvr.LocalPort) listenaddress=0.0.0.0 2>$null | Out-Null
netsh interface portproxy add v4tov4 listenport=$($nvr.LocalPort) listenaddress=0.0.0.0 connectport=$($nvr.NvrPort) connectaddress=$($nvr.NvrIp)
Write-Log ("REBUILD {0} 端口转发已重建" -f $nvr.SvcName)
}
}
'@
$healthCheckScript | Out-File -FilePath "$InstallDir\Scripts\health-check.ps1" -Encoding UTF8
# 8. 注册 Windows 计划任务(定时健康检查 + 开机自启重建转发)
Write-Host "[8/8] 注册计划任务..." -ForegroundColor Yellow
# 8.1 健康检查任务(每 5 分钟)
$healthTaskName = "NVR-Gateway-HealthCheck"
Unregister-ScheduledTask -TaskName $healthTaskName -Confirm:$false -ErrorAction SilentlyContinue
$healthAction = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File `"$InstallDir\Scripts\health-check.ps1`""
$healthTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 3650)
$healthSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable
Register-ScheduledTask -TaskName $healthTaskName -Action $healthAction -Trigger $healthTrigger -Settings $healthSettings -RunLevel Highest -Force | Out-Null
Write-Host " OK 健康检查任务: 每 5 分钟执行" -ForegroundColor Green
# 8.2 开机重建转发任务(系统启动时执行)
$bootTaskName = "NVR-Gateway-BootSetup"
Unregister-ScheduledTask -TaskName $bootTaskName -Confirm:$false -ErrorAction SilentlyContinue
$bootScript = @"
`$cfg = Get-Content '$ConfigFile' | ConvertFrom-Json
foreach (`$nvr in `$cfg.NvrList) {
netsh interface portproxy delete v4tov4 listenport=`$(`$nvr.LocalPort) listenaddress=0.0.0.0 2>`$null | Out-Null
netsh interface portproxy add v4tov4 listenport=`$(`$nvr.LocalPort) listenaddress=0.0.0.0 connectport=`$(`$nvr.NvrPort) connectaddress=`$(`$nvr.NvrIp)
}
"@
$bootScript | Out-File -FilePath "$InstallDir\Scripts\boot-setup.ps1" -Encoding UTF8
$bootAction = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File `"$InstallDir\Scripts\boot-setup.ps1`""
$bootTrigger = New-ScheduledTaskTrigger -AtStartup
$bootSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $bootTaskName -Action $bootAction -Trigger $bootTrigger -Settings $bootSettings -RunLevel Highest -Force | Out-Null
Write-Host " OK 开机重建任务: 系统启动时自动重建端口转发" -ForegroundColor Green
# 9. 生成管理脚本(manage-gateway.ps1)
# status / restart / health / logs / test 五个子命令,落地到 Scripts 目录
# 输出部署结果
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$GatewayName] 部署完成" -ForegroundColor Green
Write-Host "----------------------------------------" -ForegroundColor Gray
Write-Host "Tailscale IP : $tsIp" -ForegroundColor White
Write-Host "访问方式 :" -ForegroundColor White
foreach ($nvr in $NvrList) {
Write-Host " http://${tsIp}:$($nvr.LocalPort) -> $($nvr.NvrIp):$($nvr.NvrPort) ($($nvr.SvcName))" -ForegroundColor White
}
Write-Host "----------------------------------------" -ForegroundColor Gray
Write-Host "安装目录 : $InstallDir" -ForegroundColor White
Write-Host "配置文件 : $ConfigFile" -ForegroundColor White
Write-Host "日志目录 : $LogDir" -ForegroundColor White
Write-Host "健康检查 : 每 5 分钟自动执行" -ForegroundColor White
Write-Host "开机自启 : 系统启动时自动重建端口转发" -ForegroundColor White
Write-Host "========================================" -ForegroundColor Cyan
# 设置执行策略(首次运行需要)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# 执行部署脚本(根据仓库修改参数)
.\setup-gateway.ps1 -GatewayName "warehouse-a" -NvrConfig "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp"
# 参数说明
# -GatewayName 工控机在 Tailscale 网络中的主机名
# -TailscaleAuthKey 可选,填入后免交互自动登录
# -NvrConfig 格式 "本地端口,NVR内网IP,NVR端口,服务名;..."
cd C:\NVR-Gateway\Scripts
.\manage-gateway.ps1 status # 查看端口转发和 Tailscale 状态
.\manage-gateway.ps1 restart # 重建所有端口转发规则
.\manage-gateway.ps1 health # 手动执行健康检查
.\manage-gateway.ps1 logs # 查看最近 50 行日志
.\manage-gateway.ps1 test # 测试所有端口连通性
# 实时追踪日志
Get-Content C:\NVR-Gateway\Logs\health-check.log -Wait -Tail 20
# 查看当前端口转发规则
netsh interface portproxy show all
# 批量部署多个仓库
$warehouses = @(
@{ Name = "warehouse-a"; NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp" },
@{ Name = "warehouse-b"; NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp" },
@{ Name = "warehouse-c"; NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp" }
)
foreach ($wh in $warehouses) {
Write-Host "正在部署 $($wh.Name)..." -ForegroundColor Cyan
.\setup-gateway.ps1 -GatewayName $wh.Name -NvrConfig $wh.NvrConfig
}
{
"GatewayName": "warehouse-a",
"NvrList": [
{ "LocalPort": 8000, "NvrIp": "192.168.1.100", "NvrPort": 80, "SvcName": "nvr1-web" },
{ "LocalPort": 8554, "NvrIp": "192.168.1.100", "NvrPort": 554, "SvcName": "nvr1-rtsp" }
],
"InstallDir": "C:\NVR-Gateway",
"LogDir": "C:\NVR-Gateway\Logs"
}
编辑 C:\NVR-Gateway\config.json 后,
执行 .\manage-gateway.ps1 restart 即可生效,无需重跑部署脚本。
Windows 任务计划程序(AtStartup 触发器),系统启动时自动重建端口转发。
Trigger: AtStartup每 5 分钟检测 NVR 可达性,规则丢失时自动重新创建。
Interval: 5 minTailscale 客户端自带重连,网络恢复后自动重新加入虚拟网。
Auto ReconnectNew-NetFirewallRule 写入系统防火墙策略库,重启后依然生效。
Policy Store定时巡检 + 开机重建,互为兜底
任务计划程序每 5 分钟触发。通过 PowerShell TcpClient 检测 NVR 端口可达性;不可达时记录日志并重建转发规则,日志写入 health-check.log。
系统启动时清除可能残留的旧规则,重新创建全部转发规则,确保重启后服务立即可用。
保留最近 30 天 · 建议加入月度计划任务
| 日志文件 | 内容 |
|---|---|
| C:\NVR-Gateway\Logs\health-check.log | 健康检查记录 |
# 保留最近 30 天日志
$logDir = "C:\NVR-Gateway\Logs"
Get-ChildItem $logDir -Filter "*.log" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -Force
IP 可在 Tailscale 控制台查看,或在工控机上执行
tailscale ip -4 获取。输入后右侧地址实时生成。
安装 Tailscale 客户端,登录与工控机相同的 tailnet。
确保客户端处于连接状态(系统托盘图标为实心)。
Web 界面直接用浏览器打开;RTSP 流用 VLC 等播放器打开 rtsp://IP:8554。
{
"groups": {
"group:admin": ["[email protected]", "[email protected]"]
},
"acls": [
{
"action": "accept",
"src": ["group:admin"],
"dst": ["tag:gateway:8000,8554"]
}
],
"tagOwners": {
"tag:gateway": ["[email protected]"]
}
}
只有 group:admin 中的用户能访问工控机的 8000 / 8554 端口。
其他 tailnet 成员无法访问仓库内网任何资源。
如需给工控机打 tag,在 Tailscale 客户端设置中配置;免费版支持最多 50 台标记资源。
安全边界 = Tailscale ACL(谁能进虚拟网)+ Windows 防火墙(哪个端口能进)双重控制。
iphlpsvc(IP Helper)服务已启用192.168.1.100)Set-ExecutionPolicy RemoteSigned -Scope CurrentUsernetsh interface portproxy show all 能看到全部转发规则tailscale status 显示工控机在线http://<TS-IP>:8000 能进 NVR Webrtsp://<TS-IP>:8554 能看到画面