异地 Windows 机器网络配置与排障(防失联)
> 版本 1.1.0 | 已在真实双机跨网场景完整验证:打通、排障、并最终精简到最少依赖。
这个技能解决什么问题
一台机器放在异地长期开机——另一个办公点、老家、机房托管——人不在现场。
这时候最容易出事的是两件事,而且它们的共同点是都会害你白跑一趟几十公里:
一是误判。 你会以为它断了。可 ping 的结果在这类链路上基本不可信:Windows 公用网络防火墙、
校园网 AP 隔离、远端主机防火墙,任何一层都会静默丢掉 ICMP。更隐蔽的是本机如果跑着代理进程,
.NET/curl 会自动走代理,导致 IP 形式的地址超时而主机名形式正常。
本技能给出一套定级判定法,让「到底通没通」有唯一答案。
二是改崩。 你想给它改静态 IP、网关或 DNS,配错一个字符,它就真的断了,而且 SSH、WinRM
基本不会开——没有回退通道。本技能把「先装回滚保险再动手」做成强制流程。
配套两个可直接运行的脚本(见文末),把上述流程自动化。
更新记录
v1.1.0
- 全文脱敏:移除全部具体环境信息(地名、机构名、真实主机名与私有网段),改为通用示例。
- 新增「交付 .ps1 给别人时的规范」章节,把踩过的编码与语法校验坑固化下来。
- 两条脚本补充参数校验与失败自动回滚,-CleanupRedundantProxy 可在转接多余时安全清理。
v1.0.0
- 首版:四条铁律 + 两个运维脚本。
铁律一:不要用 ping 判断链路通不通
ping(ICMP echo)会被下面任意一层静默丢弃,超时不代表链路故障:
| 丢弃层 | 触发条件 |
|---|---|
| Windows 防火墙 | 网络位置为「公用」时默认拦入站 ICMPv4 回显 |
| 校园网 / 企业网 AP | 隔离策略直接屏蔽跨子网 ICMP |
| 远端主机防火墙 | 只放行业务端口 |
正确判定顺序(从强到弱,命中一条即可定论):
# 1) 最弱:TCP 端口握手
Test-NetConnection <目标IP> -Port <端口> -InformationLevel Quiet
# 2) 更强:真的取到业务数据
Invoke-WebRequest http://<目标IP>:<端口>/ -TimeoutSec 12 -UseBasicParsing
# → 返回 StatusCode 200 即端到端闭环证明
# 3) 隧道自身:Tailscale 侧看 direct / relay
tailscale status # active; direct <ip:port> = 打洞成功
tailscale ping <peer地址> # via <ip:port> 才算直连,via DERP 是中转
⚠️ 发请求前务必排除本机代理干扰(.NET / curl 会自动吃 http_proxy 环境变量和系统代理):
| 现象 | 说明 |
|---|---|
IP 形式 http://100.x.y.z:port/ 超时,但主机名形式 http://<name>:port/ 通 |
本机代理在作祟。.NET 默认 bypass 规则会放过「不含点的主机名」,IP 形式却被送去代理 |
Test-NetConnection TCP OPEN 但 HTTP 超时 |
同上,TCP 层才是真相 |
$env:http_proxy=$null; $env:HTTP_PROXY=$null; $env:https_proxy=$null; $env:HTTPS_PROXY=$null
> 踩坑记录:同一台机器上连续 4 次把「HTTP 超时」误判成远端故障,最后查明是
> 某进程写入的 http_proxy=http://127.0.0.1:<随机高位端口>。凡用 HTTP 下结论,先用 TCP 层交叉验证。
铁律二:动网络配置前必须先装「自动回滚保险」
异地机器改 IP/网关/DNS,配错一个字符就是当场失联,且 SSH、WinRM 通常没开。
顺序固定为:采集 → 装保险 → 改 → 复测 → 撤保险。
1. 采集现状(改之前必须留底)
$cfg = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway } | Select-Object -First 1
$cfg.InterfaceAlias, $cfg.InterfaceIndex, $cfg.NetAdapter.MacAddress
$cfg.IPv4Address.IPAddress, $cfg.IPv4Address.PrefixLength
$cfg.IPv4DefaultGateway.NextHop
(Get-DnsClientServerAddress -InterfaceIndex $cfg.InterfaceIndex -AddressFamily IPv4).ServerAddresses
DNS 列表务必抄下来 —— 改成静态 IP 时 DNS 不会自动继承,漏配会变成「能 ping IP 但打不开网页」。
2. 装保险:写回滚脚本 + 注册一次性计划任务
$rb = "C:\Users\Public\网络自动回滚.ps1"
@'
$a = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway } | Select-Object -First 1
Set-NetIPInterface -InterfaceIndex $a.InterfaceIndex -AddressFamily IPv4 -Dhcp Enabled
Remove-NetIPAddress -InterfaceIndex $a.InterfaceIndex -Confirm:$false -EA SilentlyContinue
Remove-NetRoute -InterfaceIndex $a.InterfaceIndex -Confirm:$false -EA SilentlyContinue
Set-DnsClientServerAddress -InterfaceIndex $a.InterfaceIndex -ResetServerAddresses -EA SilentlyContinue
Start-Sleep 5; ipconfig /renew | Out-Null
'@ | Set-Content $rb -Encoding UTF8
$arg = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$rb`""
Register-ScheduledTask -TaskName "网络配置应急回滚" `
-Action (New-ScheduledTaskAction -Execute powershell.exe -Argument $arg) `
-Trigger (New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(12)) `
-User SYSTEM -RunLevel Highest -Force
保险时间取 10–15 分钟:够验证,失联时也不至于干等。
3. 执行变更(顺序不能颠倒)
Set-NetIPInterface -InterfaceIndex $idx -AddressFamily IPv4 -Dhcp Disabled
Remove-NetIPAddress -InterfaceIndex $idx -Confirm:$false # 会连网关一起清掉
Remove-NetRoute -InterfaceIndex $idx -DestinationPrefix 0.0.0.0/0 -Confirm:$false
New-NetIPAddress -InterfaceIndex $idx -IPAddress <IP> -PrefixLength <位数> -DefaultGateway <网关>
Set-DnsClientServerAddress -InterfaceIndex $idx -ServerAddresses @("DNS1","DNS2")
4. 复测三件事(缺一不可)
ping <网关> -n 3 # 局域网层通
ping 114.114.114.114 -n 3 # 出网层通
Resolve-DnsName www.baidu.com # DNS 解析层通
三者全绿才算成功。只要有一项挂,什么都别做,等保险自动回滚。
5. 确认后必须手工撤销保险
Unregister-ScheduledTask -TaskName "网络配置应急回滚" -Confirm:$false
忘了撤销,倒计时一到配置会被还原 —— 这是最常见的「明明调好了第二天又不行」的原因。
铁律三:能不改 IP 就别改
先问「漂移的到底是哪个地址」,多数焦虑其实是选错了地址而不是地址会变:
| 想干什么 | 该用什么地址 | 会不会变 |
|---|---|---|
| 访问那台机器本身(远程桌面 / 文件共享 / 管理服务) | 隧道地址或 MagicDNS 名(如 my-server) |
永不变 |
| 访问机器上跑的 Web 服务 | 同上,前提见下 | 永不变 |
| 访问那台机器所在局域网的其他设备 | 真实内网 IP | 会变,才需要固定 |
常见坑:Web 服务往往只绑了网卡的具体地址,导致隧道地址反而连不上。
> 另外,校园网/家用路由器的 DHCP 对长期在线设备通常会续租同一 IP,漂移概率被高估;
> 真需要固定时,优先级是 路由器做 DHCP 保留(MAC 绑定) > 本机设静态 IP
> —— 前者保留 DHCP 的容错,后者写错就没救。
铁律四:别急着上 portproxy —— 先看清 LocalAddress 和防火墙 scope
症状:同一台机器,http://<内网IP>:<port>/ 通,但 http://<隧道IP>:<port>/ 不通。
⚠️ 不要靠现象推断。曾据此断定「服务一定绑死在具体网卡」——错的。
实测服务绑的是 0.0.0.0,真凶是一条带 LocalAddress / RemoteAddress scope 的防火墙规则
(第三方安装包很常见:给服务端口配规则时只放行局域网段,导致隧道侧被默认阻止)。
必须在目标机实测,这一步无法跳过:
Get-NetTCPConnection -LocalPort 8080 -State Listen # 关键看 LocalAddress
Get-CimInstance Win32_Process -Filter "ProcessId=<PID>" # CommandLine 常含可用线索
Get-NetFirewallRule | Where-Object { $_.LocalPorts -eq 8080 } # 看有没有现成的带 scope 规则
LocalAddress 的值 |
真凶 | 治法 |
|---|---|---|
0.0.0.0 / :: |
防火墙规则的 scope 或 profile 限制 | 新建一条无 scope 限制、-Profile Any 的放行规则即可。不需要 portproxy |
具体 IP(如 192.168.x.x) |
socket 绑死在一张网卡 | 用 portproxy 转接,或改服务监听 0.0.0.0 |
| 查不到任何监听 | 服务压根没起 | 先启服务 |
放行的正确写法(不要给 -LocalAddress,否则又造一个带 scope 的规则):
New-NetFirewallRule -DisplayName "服务端口跨网放行" -Direction Inbound `
-Protocol TCP -LocalPort 8080 -Action Allow -Profile Any
仅当 socket 确实绑死在具体地址时,才用 portproxy(不动服务代码、不重启服务、一条命令可撤销):
netsh interface portproxy add v4tov4 `
listenaddress=100.x.y.z listenport=8080 `
connectaddress=<服务绑定IP> connectport=8080
netsh interface portproxy show all # 查看
netsh interface portproxy delete v4tov4 listenaddress=100.x.y.z listenport=8080 # 撤销
要点与坑:
- 转发目标要动态取,别写死:在
Get-NetTCPConnection -LocalPort <p> -State Listen
里挑LocalAddress含.且不等于0.0.0.0的那条;若服务已通配,目标填127.0.0.1。 - 不要
listenaddress=*:会把服务已占的端口一起抢过来。 - 依赖
iphlpsvc(IP Helper)服务;被优化软件禁过的话要先恢复。 - 配置写在注册表,重启保留,但
iphlpsvc常比隧道网卡起得早 → 开机后转接会失效。
兜底:注册-AtStartup计划任务(SYSTEM),脚本内先Start-Sleep 45再重 add 一次。
无人值守机器的三件事(长期稳定性)
- Tailscale 关 key expiry:免费计划设备授权 180 天过期,到期集体离线,异地那台根本点不了。
后台Machines → 每台 → Disable key expiry。
- ★ 本地判据:tailscale status --json里Self对象没有KeyExpiry字段 = 永久有效
(字段直接消失,不是设成 0001 年)。别再去解析天数,会误判。
- 注:某些版本--json的Peer恒为空字典,peer 详情只能看文本tailscale status。 - 地址用 MagicDNS 名,不写内网 IP。
- 服务监听
0.0.0.0,别绑具体网卡地址。
子网路由的两个铁规矩
- 广播的网段必须是网关机自己所在的网段,且绝不能与接收方所在网段重叠。
重叠会导致接收方路由表里出现 metric 更小的隧道路由,把本地流量吃掉,表现为「本地网上不了」。
踩过一次:广播了接收方自己的网段,结果那台机器本地全断,只能tailscale up --accept-routes=false救回。 - 改完要在管理后台 approve,只
--advertise-routes不生效。
配套脚本
| 脚本 | 用途 |
|---|---|
scripts/set-static-ip-safe.ps1 |
把网卡改成固定 IPv4,自带自动回滚保险,防一改就失联 |
scripts/crossnet-troubleshoot.ps1 |
诊断并修复「内网 IP 能访问、隧道地址访问不到」,自动判定根因 |
均需在该 Windows 机器上以管理员 PowerShell 运行,输出会自动存到桌面的结果文件,便于回传给远程协助的人。
交付 .ps1 给别人时的规范(踩过坑)
- 必须是 UTF-8 带 BOM + CRLF,否则中文注释乱码、
Read-Host表现异常
powershell $t = [IO.File]::ReadAllText($f, [Text.UTF8Encoding]::new($false)) -replace "`r`n","`n" -replace "`n","`r`n" - 交付前跑语法检查,别让人双击才发现报错
powershell $err = $null [void][Management.Automation.Language.Parser]::ParseFile($f, [ref]$null, [ref]$err) - cmdlet 输出在远程工具里常被吞掉 → 结果
Set-Content到文件再读回来。