最近想实现在外面访问家里的迷你电脑,因为电脑的 IPv6 地址是通过 SLACC 分配的,不能在路由器上获得迷你电脑的 IPv6 地址,所以需要在电脑上实现 DDNS 功能。就写了一个脚本,实现了已下功能:

  1. 从本地网卡获取 IPv6 的第一个首选地址,通过 cloudfalre api 获取 dns 记录的 ipv6 地址
  2. 对比获取的 IPv6 地址与 cloudflare 中的 dns 记录是否一致
  3. 如果比对失败则通过 cloudflare api 更新 dns 解析
  4. 发生错误则发送 telegram 通知

下面是脚本内容:

# ------------------------------------------------------------------------------
# 脚本名称: UpdateDNS.ps1
# 描述: 获取本机 IPv6 首选地址,通过 Cloudflare API 更新 DNS 记录,并发送 Telegram 通知
# 作者: [opswill]
# 日期: [2024/12/12]
# 版本: 1.0
# ------------------------------------------------------------------------------
# 注意: 请确保已正确配置 API Token 和其他变量
# ------------------------------------------------------------------------------
# 设置 PowerShell 执行策略
Set-ExecutionPolicy Bypass -Scope Process -Force

# 配置 Cloudflare API 和 Telegram Bot 信息
$apiToken = ""
$zoneId = ""
$recordId = ""
$domain = ""
$botToken = ""
$chatId = ""

# 日志文件路径
$logFilePath = "C:\DDNS.log"  # 请替换为你的日志文件路径

function Log-Message {
    param(
        [string]$Message
    )
    $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    $logEntry = "$timestamp - $Message"
    Add-Content -Path $logFilePath -Value $logEntry
}

function Send-TelegramMessage {
    param(
        [string]$BotToken,
        [string]$ChatId,
        [string]$Message
    )

    $telegramApiUrl = "https://api.telegram.org/bot$BotToken/sendMessage"
    $body = @{
        chat_id = $ChatId
        text    = $Message
    } | ConvertTo-Json -Depth 10

    try {
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
        Invoke-WebRequest -Uri $telegramApiUrl -Method POST -Body $bytes -ContentType "application/json" -UseBasicParsing | Out-Null
    } catch {
        Log-Message "发送Telegram通知失败,错误信息:$_"
    }
}

function Get-IPv6Address {
    $ipv6Address = Get-NetIPAddress -InterfaceAlias "vEthernet (vSwitch-Public-Ethernet)" | 
                   Where-Object { $_.AddressFamily -eq "IPv6" -and $_.PrefixOrigin -eq "RouterAdvertisement" -and $_.SuffixOrigin -eq "Link" } | 
                   Select-Object -First 1 -ExpandProperty IPAddress

    if (-not $ipv6Address) {
        throw "未找到有效的 IPv6 地址。"
    }

    return $ipv6Address
}

function Get-CurrentDNSRecord {
    $uri = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId"
    $headers = @{
        "Authorization" = "Bearer $apiToken"
    }

    try {
        $response = Invoke-WebRequest -Uri $uri -Method GET -Headers $headers -UseBasicParsing
        $responseObj = $response.Content | ConvertFrom-Json
        if ($responseObj.success) {
            return $responseObj.result.content
        } else {
            throw "获取DNS记录失败,错误信息:$($responseObj.errors | ConvertTo-Json -Depth 10)"
        }
    } catch {
        throw "获取当前DNS记录失败,错误信息:$_"
    }
}

function Update-DNSRecord {
    param(
        [string]$ipv6Address
    )

    # 准备 API 请求
    $uri = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId"
    $headers = @{
        "Content-Type"  = "application/json"
        "Authorization" = "Bearer $apiToken"
    }

    # 准备请求体
    $body = @{
        name    = $domain
        ttl     = 60
        content = $ipv6Address
        type    = "AAAA"  # 使用 AAAA 记录类型更新 IPv6 地址
    } | ConvertTo-Json -Depth 10

    try {
        $response = Invoke-WebRequest -Uri $uri -Method PATCH -Headers $headers -Body $body -ContentType "application/json" -UseBasicParsing
        return $response
    } catch {
        throw "发送API请求更新DNS记录失败,错误信息:$_"
    }
}

# 记录开始时间
Log-Message "脚本开始执行"

# 获取 IPv6 地址
try {
    $ipv6Address = Get-IPv6Address
    Log-Message "从本机网卡获取到IPv6地址: $ipv6Address"
} catch {
    $message = "Windows DDNS: 获取IPv6地址失败,错误信息:$_"
    Send-TelegramMessage -BotToken $botToken -ChatId $chatId -Message $message
    Log-Message $message
    exit
}

# 获取当前 DNS 记录
try {
    $currentDNSRecord = Get-CurrentDNSRecord
    Log-Message "当前 $domain 的 DNS 记录内容: $currentDNSRecord"

    # 比较当前 DNS 记录与本机 IPv6 地址
    if ($currentDNSRecord -ne $ipv6Address) {
        Log-Message "DNS 记录与本机 IPv6 地址不一致,准备更新 DNS 记录。"
        
        # 发送 API 请求更新 DNS 记录
        $response = Update-DNSRecord -ipv6Address $ipv6Address
        $responseObj = $response.Content | ConvertFrom-Json

        if ($responseObj.success) {
            $logMessage = "成功更新 DNS 记录: $domain -> $ipv6Address"
            Log-Message $logMessage
        } else {
            $message = "Windows DDNS: 更新DNS记录失败,错误信息:$($responseObj.errors | ConvertTo-Json -Depth 10)"
            Send-TelegramMessage -BotToken $botToken -ChatId $chatId -Message $message
            Log-Message $message
        }
    } else {
        Log-Message "DNS 记录与本机 IPv6 地址一致,无需更新。"
    }
} catch {
    $message = "Windows DDNS: $_"
    Send-TelegramMessage -BotToken $botToken -ChatId $chatId -Message $message
    Log-Message $message
}

# 记录结束时间
Log-Message "脚本执行结束"

按 win + r 然后输入 taskschd.msc 创建一个新的计划任务,从而实现开机未登录和登录状态定期执行,这样就可以通过域名在外面访问家里的迷你电脑了。