Windows 下对 WireGuard 动态 DNS 解析

发布于 2024-04-22  73 次阅读


前言

对于 WireGuard DDNS 刷新,Linux 中早有脚本,OpenWrt 中也有 wireguard_watchdog 只要直接设个定时任务就行了:

*/2 * * * * /usr/bin/wireguard_watchdog

但是,对于 Windows ,虽然有客户端,但官方一直都没集成 DDNS 解析,所以这边记录一下!

临时脚本

以前图省事,临时写了一个简陋的 bat:

@echo off
:loop
ping -n 1 192.168.11.1 > nul :: 改成要 ping 通的 wg
if %errorlevel% neq 0 (
    net stop "WireGuard Tunnel: wg0" && net start "WireGuard Tunnel: wg0" :: 服务里面查看启动 wg Windows 客户端后的服务名称,一般“:”后的就是你的配置名
)
echo %DATE% %TIME% : I'M STILL ALIVE !
timeout /t 600 > nul
goto loop

pause

右键管理员身份运行就行了,大致是每隔一段时间 ping 一次 wg 目标,ping 不通就重启服务,问题有几个:

  • 不通用(基本上 wg 配置好名称就写死了)
  • 无开机启动
  • 有个界面会一直挂在任务栏中

对我日常试用来说,影响基本很小,也达到了我的要求。但在某一次 wg 目标的外网 IP 改变了,恰恰这台 Windows 又擅自给我重启了。。。杯具发生了。。。

特别感谢

https://kenvix.com/post/wireguard-ddns-windows

为什么这次写在前面了呢?因为后面的可以不看,直接照着这位大佬的抄就行了,我只是拾人牙慧,外加一些自己的感想~

脚本

基本思路是这样的,把脚本做成 Windows 服务。首先如下脚本保存为 WireGuard-Reresolve.ps1

# Copyright (C) 2021 Max Schulze. All Rights Reserved.
# Modified by Kenvix <i@kenvix.com> @ 2023/9/15
# 
# near-literal Translation of the linux version by Jason A. Donenfeld
# to decrypt the dpapi Credentials, you have to be the same user as the wireguard tunnel service, i.e. "nt authority\system", check with "whoami"
# this script might be called by task scheduler as 
#  powershell -NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -Command ./WireGuard-Reresolve.ps1 -LoopRunAsCron -DelaySeconds 600
# if you want to try it in cmd, remember to elevate the user, i.e. with psexec from sysutils 
#  psexec -s -i powershell -NoPr...
#Requires -RunAsAdministrator
param(
    [switch]$LoopRunAsCron = $false,
    [int]$DelaySeconds = 600
)
Set-StrictMode -Version 3
Add-Type -AssemblyName System.Security
function WireGuard-Reresolve {
  param(
    [Parameter(Mandatory=$true)]
    [string]$File
  )
  Set-Variable CONFIG_FILE -Value $File.ToString().Trim('"')
  if (-not (Test-Path -Path $CONFIG_FILE)) {
    throw New-Object System.IO.FileNotFoundException("Wireguard Config $CONFIG_FILE File not found.", $CONFIG_FILE)
  }
  $byteCrypted = ((Get-Content -LiteralPath $CONFIG_FILE -Encoding Byte -ReadCount 0))
  $config = [System.Security.Cryptography.ProtectedData]::Unprotect($byteCrypted,$null,[System.Security.Cryptography.DataProtectionScope]::LocalMachine)
  $config = [System.Text.UTF8Encoding]::UTF8.GetString($config)
  Set-Variable Interface -Option Constant -Value $(if ($CONFIG_FILE -match '.?([a-zA-Z0-9_=+.-]{1,64})\.conf.dpapi$') { $matches[1] } else { $null })
  function process_peer () {
    if (-not $PEER_SECTION -or ($PUBLIC_KEY -eq $null) -or ($ENDPOINT -eq $null)) { return }
    if (-not ((& wg show "$INTERFACE" latest-handshakes) -replace $PUBLIC_KEY -match ('[0-9]+'))) { return }
    if (((Get-Date) - (New-Object -Type DateTime -ArgumentList 1970,1,1,0,0,0,0).AddSeconds($matches[0]).ToLocalTime()).TotalSeconds -le 135) { return }
    (& wg set "$INTERFACE" peer "$PUBLIC_KEY" endpoint "$ENDPOINT")
    reset_peer_section
  }
  function reset_peer_section () {
    Set-Variable PEER_SECTION -Value $null
    Set-Variable PUBLIC_KEY -Value $null
    Set-Variable ENDPOINT -Value $null
  }
  reset_peer_section
  Set-Variable PEER_SECTION -Value $null
  foreach ($line in $config.Split([Environment]::NewLine,[StringSplitOptions]::RemoveEmptyEntries)) 
  {
    if ($line.Trim().length -gt 0) {
      $stripped = $line.Trim() -ireplace '\#.*'
      $key = $stripped -ireplace '=.*'; $key = $key.Trim()
      $val = $stripped -ireplace '^.*?='; $val = $val.Trim()
      if ($key -match '\[.*') { process_peer; reset_peer_section; }
      if ($key -eq '[Peer]') { $PEER_SECTION = $true }
      if ($PEER_SECTION) {
        switch ($key) {
          "PublicKey" { $PUBLIC_KEY = $val; continue; }
          "Endpoint" { $ENDPOINT = $val; continue; }
        }
      }
    }
  }
  process_peer
}
function WireGuard-Reresolve-All-Active {
  Get-Service -Name "WireGuardTunnel$*" `
   | Where-Object {$_.Status -eq "Running"} `
   | ForEach-Object { $_.Name.Substring(16) } `
   | ForEach-Object { Get-ChildItem -File "$env:programfiles\wireguard\data\configurations\$_.conf.dpapi" } `
   | ForEach-Object { WireGuard-Reresolve $_.FullName}
}
if ($LoopRunAsCron) {
  echo "Running as Cron DelaySeconds=$DelaySeconds"
  while ($true) {
    WireGuard-Reresolve-All-Active
    Start-Sleep -Seconds $DelaySeconds
  }
}

这个脚本同 wg 官方的脚本一样,也是检查 wg keepalive 的时间,如果超过了规定值就重新解析一下 DDNS 服务!

设置 powershell 权限

管理员身份运行 powershell,输入如下命令:

Set-ExecutionPolicy RemoteSigned

回车后输入 y 再回车,确认更改策略。

做成服务

这一步是最最最坑的,我差点放弃!🤦‍

因为本着能懒则懒的原则,不想多下一个应用,首先 cmd 管理员中用 SC 添加了服务,结果启动失败!然后 powershell 管理员中用 New-Service 添加了服务,启动依然不行!而大佬的备注中有一句:

该脚本必须以 SYSTEM 用户身份执行,否则无法解密 WireGuard 的配置文件。

我曾一度为服务如何试用 SYSTEM 账户运行而找原因。。。事实证明这也是我遇到的问题之一,但要靠后的多。。。

最后,还是下载了 shawl ,只是个单文件,找个目录放就行了!之后管理员身份运行 cmd,定位到 shawl 文件所在目录下,添加下服务:

shawl add --name WireGuard-Reresolve -- powershell -NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -Command "D:\wg\WireGuard-Reresolve.ps1" -LoopRunAsCron -DelaySeconds 250
注意,"D:\wg\WireGuard-Reresolve.ps1" 这里的路径改成你自己的脚本路径!

奇迹发生,服务居然启动起来了。。。。。。

对服务配置 SYSTEM 权限

首先 Win+R 打开运行框,输入 services.msc 打开服务窗口,选择我们刚刚添加的服务 WireGuard-Reresolve 双击编辑,切换到登录框配置如下:

选择此账户,输入 nt authority\system 即 SYSTEM 账户,然后输入你的登录密码确认,就更改服务了!可以点启动,启动下服务,看看进程里面是否以 SYSTEM 运行了!

shawl.exe 同目录下会自动生成日志,可以关注运行情况!