Continued WireGuard troubleshooting

This commit is contained in:
2026-01-12 20:20:48 -07:00
parent 28fd0401e3
commit 4aff4ba8c0
3 changed files with 936 additions and 161 deletions

View File

@@ -523,6 +523,10 @@ $wireGuardInstallerDir = Join-Path $depsRoot 'VPN_Tunnel_Adapter'
$wireGuardBootstrapperName = 'wireguard-installer.exe' $wireGuardBootstrapperName = 'wireguard-installer.exe'
$wireGuardBootstrapperPath = Join-Path $wireGuardInstallerDir $wireGuardBootstrapperName $wireGuardBootstrapperPath = Join-Path $wireGuardInstallerDir $wireGuardBootstrapperName
$wireGuardMsiVersion = '0.5.3' $wireGuardMsiVersion = '0.5.3'
$wireGuardTunnelLegacyName = 'BorealisWireGuardTunnel'
$wireGuardTunnelNameInternal = 'Borealis'
$wireGuardTunnelNameFriendly = 'Borealis'
$wireGuardTunnelBootstrapAddress = '169.254.255.254/32'
$wireGuardMsiFiles = @{ $wireGuardMsiFiles = @{
'X64' = "wireguard-amd64-$wireGuardMsiVersion.msi" 'X64' = "wireguard-amd64-$wireGuardMsiVersion.msi"
'AMD64' = "wireguard-amd64-$wireGuardMsiVersion.msi" 'AMD64' = "wireguard-amd64-$wireGuardMsiVersion.msi"
@@ -702,6 +706,10 @@ function Install_Agent_Dependencies {
ServicePresent = $false ServicePresent = $false
DriverPresent = $false DriverPresent = $false
DriverPaths = @() DriverPaths = @()
AdapterPresent = $false
AdapterNames = @()
DedicatedAdapterPresent = $false
DedicatedAdapterNames = @()
} }
$exeCandidates = @( $exeCandidates = @(
@@ -748,6 +756,29 @@ function Install_Agent_Dependencies {
$state.DriverPaths = $driverHits | Select-Object -Unique $state.DriverPaths = $driverHits | Select-Object -Unique
} }
try {
$adapters = Get-WireGuardAdapters
if ($adapters) {
$state.AdapterPresent = $true
$state.AdapterNames = $adapters | Select-Object -ExpandProperty Name -Unique
$state.DriverPresent = $true
$state.Installed = $true
}
$dedicated = @()
foreach ($adapter in ($adapters | Where-Object { $_ })) {
if (Test-WireGuardAdapterName -Adapter $adapter -ExpectedName $wireGuardTunnelNameFriendly) {
$dedicated += $adapter
}
}
if ($dedicated.Count -gt 0) {
$state.DedicatedAdapterPresent = $true
$state.DedicatedAdapterNames = $dedicated | Select-Object -ExpandProperty Name -Unique
$state.DriverPresent = $true
$state.Installed = $true
}
} catch {}
$uninstallRoots = @( $uninstallRoots = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
@@ -836,29 +867,51 @@ function Install_Agent_Dependencies {
) )
$pair = @{ PrivateKey = $null; PublicKey = $null; Source = $null } $pair = @{ PrivateKey = $null; PublicKey = $null; Source = $null }
$cliCandidates = @() $wgCli = $null
if ($WgExe) { $cliCandidates += $WgExe } if ($WgExe -and (Test-Path $WgExe -PathType Leaf)) { $wgCli = $WgExe }
if ($WireGuardExe) { $cliCandidates += $WireGuardExe }
foreach ($cli in $cliCandidates) { $priv = $null
if (-not $cli -or -not (Test-Path $cli -PathType Leaf)) { continue } if ($wgCli) {
try { $priv = (& $wgCli genkey) } catch {}
if ($priv) {
$priv = $priv.Trim()
$pair.PrivateKey = $priv
$pair.Source = $wgCli
try {
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $wgCli
$psi.Arguments = 'pubkey'
$psi.RedirectStandardInput = $true
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
if ($psi.PSObject.Properties.Name -contains 'StandardInputEncoding') {
$psi.StandardInputEncoding = [System.Text.Encoding]::ASCII
}
if ($psi.PSObject.Properties.Name -contains 'StandardOutputEncoding') {
$psi.StandardOutputEncoding = [System.Text.Encoding]::ASCII
}
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $psi
$proc.Start() | Out-Null
$proc.StandardInput.WriteLine($priv)
$proc.StandardInput.Close()
$pub = $proc.StandardOutput.ReadToEnd()
$proc.WaitForExit()
if ($proc.ExitCode -eq 0 -and $pub) {
$pair.PublicKey = $pub.Trim()
}
} catch {}
}
}
if (-not $pair.PrivateKey -and $WireGuardExe -and (Test-Path $WireGuardExe -PathType Leaf)) {
try { try {
# Only wg.exe supports genkey; wireguard.exe likely won't, but attempt anyway $priv = (& $WireGuardExe genkey)
$priv = (& $cli genkey)
if ($priv) { if ($priv) {
$priv = $priv.Trim() $pair.PrivateKey = $priv.Trim()
$pair.PrivateKey = $priv $pair.Source = $WireGuardExe
$pair.Source = $cli
try {
$padLen = 4 - ($priv.Length % 4)
$privForPub = $priv
if ($padLen -lt 4) {
$privForPub = $priv + ('=' * $padLen)
}
$pub = ($privForPub | & $cli pubkey)
if ($pub) { $pair.PublicKey = $pub.Trim() }
} catch {}
break
} }
} catch {} } catch {}
} }
@@ -878,6 +931,504 @@ function Install_Agent_Dependencies {
return $pair return $pair
} }
function Find-WireGuardDriverInf {
param(
[Parameter()][string]$LogName = 'Install.log'
)
$logPrefix = '[WireGuard]'
$output = $null
try {
$output = & pnputil.exe /enum-drivers
} catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix pnputil /enum-drivers failed: {0}" -f $_.Exception.Message)
return $null
}
if (-not $output) { return $null }
$published = $null
$original = $null
$provider = $null
foreach ($line in ($output -split "`r?`n")) {
if (-not $line.Trim()) {
if ($published -and ($original -match '^(wireguard|wintun)\.inf$' -or $provider -like '*WireGuard*')) {
return $published
}
$published = $null
$original = $null
$provider = $null
continue
}
if ($line -match 'Published Name\s*:\s*(\S+)') {
$published = $Matches[1].Trim()
continue
}
if ($line -match 'Original Name\s*:\s*(\S+)') {
$original = $Matches[1].Trim()
continue
}
if ($line -match 'Provider Name\s*:\s*(.+)') {
$provider = $Matches[1].Trim()
continue
}
}
if ($published -and ($original -match '^(wireguard|wintun)\.inf$' -or $provider -like '*WireGuard*')) {
return $published
}
return $null
}
function Get-WireGuardAdapters {
param([string]$NameFilter)
$args = @{
Namespace = 'root/StandardCimv2'
ClassName = 'MSFT_NetAdapter'
ErrorAction = 'SilentlyContinue'
}
if ($NameFilter) {
$args.Filter = "Name='$NameFilter'"
} else {
$args.Filter = "InterfaceDescription LIKE '%WireGuard%'"
}
try {
$opTimeout = (Get-Command Get-CimInstance).Parameters.ContainsKey('OperationTimeoutSec')
if ($opTimeout) { $args.OperationTimeoutSec = 10 }
} catch {}
try {
return Get-CimInstance @args
} catch {
return $null
}
}
function Test-WireGuardAdapterName {
param(
[Parameter()][object]$Adapter,
[Parameter(Mandatory = $true)][string]$ExpectedName
)
if (-not $Adapter -or -not $ExpectedName) { return $false }
$adapterName = $Adapter.Name
if (-not $adapterName) { return $false }
if ($adapterName.ToString().Trim().ToLowerInvariant() -ne $ExpectedName.ToLowerInvariant()) {
return $false
}
$desc = $Adapter.InterfaceDescription
if (-not $desc) {
return $false
}
if ($desc.ToString() -notlike '*WireGuard*') {
return $false
}
return $true
}
function Get-WireGuardAdapterByName {
param([string]$AdapterName)
if (-not $AdapterName) { return $null }
try {
$adapters = Get-WireGuardAdapters -NameFilter $AdapterName
if ($adapters) {
foreach ($adapter in @($adapters)) {
if (Test-WireGuardAdapterName -Adapter $adapter -ExpectedName $AdapterName) {
return $adapter
}
}
}
} catch {}
return $null
}
function Rename-WireGuardAdapterName {
param(
[Parameter(Mandatory = $true)][string]$OldName,
[Parameter(Mandatory = $true)][string]$NewName,
[Parameter()][string]$LogName = 'Install.log'
)
$logPrefix = '[WireGuard]'
if ($OldName -eq $NewName) { return $true }
$args = "interface set interface name=`"$OldName`" newname=`"$NewName`""
$proc = Start-Process -FilePath 'netsh.exe' -ArgumentList $args -PassThru -WindowStyle Hidden -ErrorAction SilentlyContinue
if (-not $proc) {
Write-AgentLog -FileName $LogName -Message "$logPrefix netsh rename failed to start."
return $false
}
if (-not $proc.WaitForExit(10000)) {
try { $proc.Kill() } catch {}
Write-AgentLog -FileName $LogName -Message "$logPrefix netsh rename timed out."
return $false
}
if ($proc.ExitCode -ne 0) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix netsh rename failed with exit code {0}." -f $proc.ExitCode)
return $false
}
Write-AgentLog -FileName $LogName -Message ("$logPrefix Renamed adapter {0} -> {1}" -f $OldName, $NewName)
return $true
}
function Remove-WireGuardTunnelService {
param(
[Parameter(Mandatory = $true)][string]$TunnelName,
[Parameter(Mandatory = $true)][string]$WireGuardExe,
[Parameter()][string]$LogName = 'Install.log'
)
if (-not $TunnelName) { return }
$logPrefix = '[WireGuard]'
$serviceName = "WireGuardTunnel$TunnelName"
Write-AgentLog -FileName $LogName -Message ("$logPrefix Cleaning tunnel service {0}" -f $serviceName)
$serviceExists = $false
try {
$svc = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($svc) { $serviceExists = $true }
} catch {}
if ($serviceExists) {
try {
$proc = Start-Process -FilePath 'sc.exe' -ArgumentList 'stop', $serviceName -PassThru -WindowStyle Hidden -ErrorAction SilentlyContinue
if ($proc) {
if (-not $proc.WaitForExit(10000)) {
try { $proc.Kill() } catch {}
}
}
} catch {}
} else {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Tunnel service {0} not present; skipping stop/uninstall." -f $serviceName)
}
if ($serviceExists -and $WireGuardExe -and (Test-Path $WireGuardExe -PathType Leaf)) {
try {
$proc = Start-Process -FilePath $WireGuardExe -ArgumentList @('/uninstalltunnelservice', $TunnelName) -PassThru -WindowStyle Hidden -ErrorAction SilentlyContinue
if ($proc) {
if (-not $proc.WaitForExit(15000)) {
try { $proc.Kill() } catch {}
Write-AgentLog -FileName $LogName -Message ("$logPrefix /uninstalltunnelservice timed out for {0}" -f $TunnelName)
} else {
Write-AgentLog -FileName $LogName -Message ("$logPrefix /uninstalltunnelservice exit code for {0}: {1}" -f $TunnelName, $proc.ExitCode)
}
}
} catch {}
}
if ($serviceExists) {
try {
$proc = Start-Process -FilePath 'sc.exe' -ArgumentList 'delete', $serviceName -PassThru -WindowStyle Hidden -ErrorAction SilentlyContinue
if ($proc) {
if (-not $proc.WaitForExit(10000)) {
try { $proc.Kill() } catch {}
} else {
Write-AgentLog -FileName $LogName -Message ("$logPrefix sc delete exit code for {0}: {1}" -f $serviceName, $proc.ExitCode)
}
}
} catch {}
}
try {
$confPaths = Get-WireGuardConfigPaths -TunnelName $TunnelName
foreach ($confPath in $confPaths) {
if ($confPath -and (Test-Path $confPath -PathType Leaf)) {
Remove-Item -Path $confPath -Force -ErrorAction SilentlyContinue
Write-AgentLog -FileName $LogName -Message ("$logPrefix Removed tunnel config {0}" -f $confPath)
}
}
} catch {}
}
function Get-WireGuardConfigPaths {
param([string]$TunnelName)
if (-not $TunnelName) { return @() }
$paths = @()
$borealisConfigDir = $null
try {
if ($scriptDir) {
$borealisConfigDir = Join-Path $scriptDir 'Agent\Borealis\Settings\WireGuard'
}
} catch {}
if ($borealisConfigDir) {
$paths += $borealisConfigDir
}
if ($env:ProgramFiles) {
$paths += (Join-Path $env:ProgramFiles 'WireGuard\Data\Configurations')
}
$programDataRoot = if ($env:ProgramData) { $env:ProgramData } else { 'C:\ProgramData' }
if ($programDataRoot) {
$paths += (Join-Path $programDataRoot 'Borealis\WireGuard\Configurations')
}
if ($wireGuardInstallerDir) {
$paths += $wireGuardInstallerDir
}
$paths = $paths | Where-Object { $_ }
$confPaths = @()
foreach ($dir in $paths) {
$confPaths += (Join-Path $dir "$TunnelName.conf")
}
return $confPaths | Select-Object -Unique
}
function Get-WireGuardConfigPath {
param([string]$TunnelName)
if (-not $TunnelName) { return $null }
$confPaths = Get-WireGuardConfigPaths -TunnelName $TunnelName
foreach ($confPath in $confPaths) {
if (-not $confPath) { continue }
$dir = Split-Path $confPath -Parent
try {
if ($dir -and -not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
} catch {}
if ($dir -and (Test-Path $dir)) {
return $confPath
}
}
return $confPaths | Select-Object -First 1
}
function Get-WireGuardTunnelServiceConfigPath {
param([string]$ServiceName)
if (-not $ServiceName) { return $null }
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName"
try {
$imagePath = (Get-ItemProperty -Path $regPath -Name ImagePath -ErrorAction Stop).ImagePath
} catch {
return $null
}
if (-not $imagePath) { return $null }
$text = $imagePath.ToString()
if ($text -match '(?i)/tunnelservice\s+"([^"]+)"') {
return $Matches[1]
}
if ($text -match '(?i)/tunnelservice\s+(\S+)') {
return $Matches[1]
}
return $null
}
function Ensure-WireGuardTunnelAdapter {
param(
[Parameter(Mandatory = $true)][string]$WireGuardExe,
[Parameter()][string]$LogName = 'Install.log'
)
$logPrefix = '[WireGuard]'
$friendlyName = $wireGuardTunnelNameFriendly
$internalName = $wireGuardTunnelNameInternal
$serviceName = "WireGuardTunnel$internalName"
Write-AgentLog -FileName $LogName -Message ("$logPrefix Ensuring tunnel adapter: {0}" -f $friendlyName)
$existing = Get-WireGuardAdapterByName -AdapterName $friendlyName
if ($existing) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Adapter already present: {0}" -f $friendlyName)
return $true
}
$internalAdapter = Get-WireGuardAdapterByName -AdapterName $internalName
if ($internalAdapter) {
if ($friendlyName -and $friendlyName -ne $internalName) {
$renamed = Rename-WireGuardAdapterName -OldName $internalName -NewName $friendlyName -LogName $LogName
if ($renamed) { return $true }
return $false
}
return $true
}
try {
$wgCliExe = $null
try {
$wgCliCandidate = Join-Path (Split-Path $WireGuardExe -Parent) 'wg.exe'
if (Test-Path $wgCliCandidate -PathType Leaf) { $wgCliExe = $wgCliCandidate }
} catch {}
$serviceConfigPath = Get-WireGuardTunnelServiceConfigPath -ServiceName $serviceName
$existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
$servicePresent = $false
if ($existingService -or $serviceConfigPath) { $servicePresent = $true }
if ($servicePresent) {
$serviceConfigPath = Get-WireGuardTunnelServiceConfigPath -ServiceName $serviceName
if ($serviceConfigPath -and -not (Test-Path $serviceConfigPath -PathType Leaf)) {
$keyPair = New-WireGuardKeyPair -WireGuardExe $WireGuardExe -WgExe $wgCliExe
if ($keyPair.PrivateKey) {
$conf = @"
[Interface]
PrivateKey = $($keyPair.PrivateKey.Trim())
Address = $wireGuardTunnelBootstrapAddress
ListenPort = 0
"@
try {
$dir = Split-Path $serviceConfigPath -Parent
if ($dir -and -not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
Set-Content -Path $serviceConfigPath -Value $conf -Encoding ASCII -Force
if (Test-Path $serviceConfigPath -PathType Leaf) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Created adapter config at {0}" -f $serviceConfigPath)
}
} catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Failed to write adapter config at {0}: {1}" -f $serviceConfigPath, $_.Exception.Message)
}
}
}
Write-AgentLog -FileName $LogName -Message "$logPrefix Tunnel service already present; restarting to provision adapter."
try { Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue } catch {}
try { Start-Service -Name $serviceName -ErrorAction SilentlyContinue } catch {}
for ($i = 0; $i -lt 10; $i++) {
Start-Sleep -Milliseconds 500
if (Get-WireGuardAdapterByName -AdapterName $friendlyName) { return $true }
}
Write-AgentLog -FileName $LogName -Message "$logPrefix Tunnel service present but adapter missing; uninstalling before reinstall."
}
Remove-WireGuardTunnelService -TunnelName $internalName -WireGuardExe $WireGuardExe -LogName $LogName
if ($wireGuardTunnelLegacyName -and $wireGuardTunnelLegacyName -ne $internalName) {
Remove-WireGuardTunnelService -TunnelName $wireGuardTunnelLegacyName -WireGuardExe $WireGuardExe -LogName $LogName
}
for ($i = 0; $i -lt 10; $i++) {
Start-Sleep -Milliseconds 500
$stillPresent = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
$stillConfig = Get-WireGuardTunnelServiceConfigPath -ServiceName $serviceName
if (-not $stillPresent -and -not $stillConfig) { break }
}
$serviceStillPresent = $false
if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { $serviceStillPresent = $true }
if (Get-WireGuardTunnelServiceConfigPath -ServiceName $serviceName) { $serviceStillPresent = $true }
if ($serviceStillPresent) {
Write-AgentLog -FileName $LogName -Message "$logPrefix Tunnel service still present after uninstall; skipping reinstall to avoid WireGuard dialog."
return $false
}
try {
if (-not (Test-Path $wireGuardInstallerDir)) {
New-Item -ItemType Directory -Path $wireGuardInstallerDir -Force | Out-Null
}
} catch {}
$keyPair = New-WireGuardKeyPair -WireGuardExe $WireGuardExe -WgExe $wgCliExe
if (-not $keyPair.PrivateKey) {
Write-AgentLog -FileName $LogName -Message "$logPrefix Failed to generate WireGuard keypair for adapter provision."
return $false
}
$conf = @"
[Interface]
PrivateKey = $($keyPair.PrivateKey.Trim())
Address = $wireGuardTunnelBootstrapAddress
ListenPort = 0
"@
$confPath = $null
$confPaths = Get-WireGuardConfigPaths -TunnelName $internalName
if ($serviceConfigPath) {
$confPaths = @($serviceConfigPath) + ($confPaths | Where-Object { $_ -ne $serviceConfigPath })
}
foreach ($candidate in $confPaths) {
if (-not $candidate) { continue }
$dir = Split-Path $candidate -Parent
try {
if ($dir -and -not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
} catch {}
try {
Set-Content -Path $candidate -Value $conf -Encoding ASCII -Force
if (Test-Path $candidate -PathType Leaf) {
$confPath = $candidate
Write-AgentLog -FileName $LogName -Message ("$logPrefix Created adapter config at {0}" -f $confPath)
$borealisConfigDir = $null
try {
if ($scriptDir) {
$borealisConfigDir = Join-Path $scriptDir 'Agent\Borealis\Settings\WireGuard'
}
} catch {}
if ($borealisConfigDir) {
try {
$confFull = [System.IO.Path]::GetFullPath($confPath)
$borealisFull = [System.IO.Path]::GetFullPath($borealisConfigDir)
if ($confFull.ToLowerInvariant().StartsWith($borealisFull.ToLowerInvariant()) -eq $false) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Adapter config stored outside Borealis settings: {0}" -f $confPath)
}
} catch {}
}
break
}
Write-AgentLog -FileName $LogName -Message ("$logPrefix Adapter config not found after write: {0}" -f $candidate)
} catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Failed to write adapter config at {0}: {1}" -f $candidate, $_.Exception.Message)
}
}
if (-not $confPath) {
Write-AgentLog -FileName $LogName -Message "$logPrefix Failed to write adapter config to any candidate path."
return $false
}
$svcPreInstall = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
$svcConfig = Get-WireGuardTunnelServiceConfigPath -ServiceName $serviceName
if (-not $svcPreInstall -and -not $svcConfig) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Installing tunnel service {0} for adapter provisioning with config {1}." -f $serviceName, $confPath)
$installArgs = "/installtunnelservice `"$confPath`""
$proc = Start-Process -FilePath $WireGuardExe -ArgumentList $installArgs -PassThru -WindowStyle Hidden -ErrorAction SilentlyContinue
if (-not $proc) {
Write-AgentLog -FileName $LogName -Message "$logPrefix /installtunnelservice failed to start."
return $false
}
$installTimeoutMs = 60000
if (-not $proc.WaitForExit($installTimeoutMs)) {
try { $proc.Kill() } catch {}
Write-AgentLog -FileName $LogName -Message "$logPrefix /installtunnelservice timed out."
$svcAfterTimeout = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if (-not $svcAfterTimeout) {
return $false
}
Write-AgentLog -FileName $LogName -Message "$logPrefix Tunnel service present after /installtunnelservice timeout."
} else {
$installExit = $proc.ExitCode
Write-AgentLog -FileName $LogName -Message ("$logPrefix /installtunnelservice exit code: {0}" -f $installExit)
if ($installExit -ne 0) {
$svcAfterExit = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if (-not $svcAfterExit) {
return $false
}
Write-AgentLog -FileName $LogName -Message "$logPrefix Tunnel service present despite non-zero /installtunnelservice exit code."
}
}
} else {
Write-AgentLog -FileName $LogName -Message "$logPrefix Tunnel service already present; skipping /installtunnelservice."
}
try { Start-Service -Name $serviceName -ErrorAction SilentlyContinue } catch {}
} catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Adapter provisioning failed: {0}" -f $_.Exception.Message)
return $false
}
$finalAdapter = $null
$internalAdapter = $null
$attempts = 60
for ($i = 0; $i -lt $attempts; $i++) {
Start-Sleep -Milliseconds 500
$finalAdapter = Get-WireGuardAdapterByName -AdapterName $friendlyName
if ($finalAdapter) { return $true }
$internalAdapter = Get-WireGuardAdapterByName -AdapterName $internalName
if ($internalAdapter -and $friendlyName -and $friendlyName -ne $internalName) {
Rename-WireGuardAdapterName -OldName $internalName -NewName $friendlyName -LogName $LogName | Out-Null
$finalAdapter = Get-WireGuardAdapterByName -AdapterName $friendlyName
if ($finalAdapter) { return $true }
}
}
if ($internalAdapter -and -not $finalAdapter) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Tunnel adapter present as {0}, but rename to {1} did not complete." -f $internalName, $friendlyName)
}
return $false
}
function Ensure-WireGuardDriver { function Ensure-WireGuardDriver {
param( param(
[Parameter(Mandatory = $true)][string]$WireGuardExe, [Parameter(Mandatory = $true)][string]$WireGuardExe,
@@ -914,125 +1465,40 @@ function Install_Agent_Dependencies {
} }
$state = $stateAfterManager $state = $stateAfterManager
$wgCliExe = $null
try { try {
$wgCliCandidate = Join-Path (Split-Path $WireGuardExe -Parent) 'wg.exe' $adapterProvisioned = Ensure-WireGuardTunnelAdapter -WireGuardExe $WireGuardExe -LogName $LogName
if (Test-Path $wgCliCandidate -PathType Leaf) { $wgCliExe = $wgCliCandidate } if (-not $adapterProvisioned) {
} catch {} Write-AgentLog -FileName $LogName -Message "$logPrefix Adapter provisioning did not complete."
$bootstrapName = 'BorealisBootstrap'
$bootstrapDir = Join-Path $env:TEMP 'Borealis-WireGuard-Bootstrap'
$bootstrapConf = Join-Path $bootstrapDir "$bootstrapName.conf"
try {
if (-not (Test-Path $bootstrapDir)) {
New-Item -ItemType Directory -Path $bootstrapDir -Force | Out-Null
}
} catch {}
$keyPair = New-WireGuardKeyPair -WireGuardExe $WireGuardExe -WgExe $wgCliExe
if (-not $keyPair.PrivateKey) {
$msg = "$logPrefix Failed to generate WireGuard keypair for driver bootstrap."
Write-AgentLog -FileName $LogName -Message $msg
throw $msg
}
$peerKey = if ($keyPair.PublicKey) { $keyPair.PublicKey } else { $keyPair.PrivateKey }
$conf = @"
[Interface]
PrivateKey = $($keyPair.PrivateKey)
Address = 10.255.255.2/32
ListenPort = 0
"@
if ($keyPair.PublicKey) {
$conf += @"
[Peer]
PublicKey = $peerKey
AllowedIPs = 10.255.255.1/32
"@
}
try {
Set-Content -Path $bootstrapConf -Value $conf -Encoding ASCII -Force
Write-AgentLog -FileName $LogName -Message ("$logPrefix Created driver bootstrap config at {0}" -f $bootstrapConf)
} catch {
$msg = "$logPrefix Failed to write bootstrap config: $($_.Exception.Message)"
Write-AgentLog -FileName $LogName -Message $msg
throw $msg
}
$serviceName = "WireGuardTunnel$bootstrapName"
try {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Installing bootstrap tunnel service to seed driver ({0})" -f $serviceName)
& $WireGuardExe /installtunnelservice $bootstrapConf | Out-Null
$installExit = $LASTEXITCODE
Write-AgentLog -FileName $LogName -Message ("$logPrefix /installtunnelservice exit code: {0}" -f $installExit)
if ($installExit -ne 0) {
throw "wireguard.exe /installtunnelservice failed with exit code $installExit"
} }
} catch { } catch {
$msg = "$logPrefix Failed to install bootstrap tunnel service: $($_.Exception.Message)" Write-AgentLog -FileName $LogName -Message ("$logPrefix Adapter provisioning failed: {0}" -f $_.Exception.Message)
Write-AgentLog -FileName $LogName -Message $msg
throw $msg
} }
Start-Sleep -Seconds 2
try {
& $WireGuardExe /uninstalltunnelservice $bootstrapName | Out-Null
$uninstallExit = $LASTEXITCODE
Write-AgentLog -FileName $LogName -Message ("$logPrefix /uninstalltunnelservice exit code: {0}" -f $uninstallExit)
Write-AgentLog -FileName $LogName -Message ("$logPrefix Removed bootstrap tunnel service {0}" -f $serviceName)
} catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Cleanup of bootstrap tunnel service failed: {0}" -f $_.Exception.Message)
}
try { Remove-Item -Path $bootstrapConf -Force -ErrorAction SilentlyContinue } catch {}
try { Remove-Item -Path $bootstrapDir -Recurse -Force -ErrorAction SilentlyContinue } catch {}
$post = Get-WireGuardInstallState $post = Get-WireGuardInstallState
$postDriver = $post.DriverPresent $postDriver = $post.DriverPresent
$postMsg = "[WireGuard] Driver presence after bootstrap: $postDriver (Exe: $WireGuardExe)" $postMsg = "[WireGuard] Driver presence after adapter provisioning: $postDriver (Exe: $WireGuardExe)"
Write-AgentLog -FileName $LogName -Message $postMsg Write-AgentLog -FileName $LogName -Message $postMsg
if ($postDriver) { return } if ($postDriver) { return }
# Fallback: install Wintun driver directly via pnputil using the official Wintun package $publishedInf = Find-WireGuardDriverInf -LogName $LogName
try { if ($publishedInf) {
Ensure-WireGuardInstallerFile -Url $wintunDownloadUrl -DestinationPath $wintunZipPath -LogName $LogName $infPath = Join-Path $env:WINDIR "INF\$publishedInf"
} catch { if (Test-Path $infPath -PathType Leaf) {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Failed to cache Wintun zip: {0}" -f $_.Exception.Message)
}
if (Test-Path $wintunZipPath -PathType Leaf) {
$extractRoot = Join-Path $env:TEMP 'Borealis-Wintun'
try { Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $extractRoot } catch {}
try { New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null } catch {}
try {
Expand-Archive -Path $wintunZipPath -DestinationPath $extractRoot -Force
Write-AgentLog -FileName $LogName -Message ("$logPrefix Expanded Wintun zip to {0}" -f $extractRoot)
} catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Failed to expand Wintun zip: {0}" -f $_.Exception.Message)
}
$infPath = Get-ChildItem -Path $extractRoot -Recurse -Filter '*.inf' -ErrorAction SilentlyContinue | Select-Object -First 1
if ($infPath -and (Test-Path $infPath.FullName -PathType Leaf)) {
try { try {
Write-AgentLog -FileName $LogName -Message ("$logPrefix Installing Wintun driver via pnputil: {0}" -f $infPath.FullName) Write-AgentLog -FileName $LogName -Message ("$logPrefix Installing WireGuard driver via pnputil: {0}" -f $infPath)
pnputil.exe /add-driver "`"$($infPath.FullName)`"" /install | Out-Null pnputil.exe /add-driver "`"$infPath`"" /install | Out-Null
$postPnP = Get-WireGuardInstallState
$postPnPDriver = $postPnP.DriverPresent
Write-AgentLog -FileName $LogName -Message ("$logPrefix Driver presence after pnputil install: $postPnPDriver")
if (-not $postPnPDriver) {
throw "$logPrefix pnputil install completed but driver still missing."
}
return
} catch { } catch {
Write-AgentLog -FileName $LogName -Message ("$logPrefix pnputil driver install failed: {0}" -f $_.Exception.Message) Write-AgentLog -FileName $LogName -Message ("$logPrefix pnputil driver install failed: {0}" -f $_.Exception.Message)
} }
} else {
Write-AgentLog -FileName $LogName -Message "$logPrefix No Wintun INF found in extracted package."
} }
} }
throw "$logPrefix Driver still missing after bootstrap and pnputil fallback." $postPnP = Get-WireGuardInstallState
$postPnPDriver = $postPnP.DriverPresent
Write-AgentLog -FileName $LogName -Message ("$logPrefix Driver presence after pnputil install: {0}" -f $postPnPDriver)
if ($postPnPDriver) { return }
throw "$logPrefix Driver still missing after adapter provisioning and pnputil fallback."
} }
# AutoHotKey portable # AutoHotKey portable
@@ -1113,12 +1579,18 @@ AllowedIPs = 10.255.255.1/32
$state = Get-WireGuardInstallState $state = Get-WireGuardInstallState
$stateVersion = if ($state.Version) { $state.Version } else { 'unknown' } $stateVersion = if ($state.Version) { $state.Version } else { 'unknown' }
$stateExe = if ($state.ExePath) { $state.ExePath } else { 'n/a' } $stateExe = if ($state.ExePath) { $state.ExePath } else { 'n/a' }
$stateSummary = "[WireGuard] Detected install state: Installed={0}; Version={1}; Service={2}; Driver={3}; Exe={4}" -f ` $stateSummary = "[WireGuard] Detected install state: Installed={0}; Version={1}; Service={2}; Driver={3}; Adapter={4}; BorealisAdapter={5}; Exe={6}" -f `
$state.Installed, $stateVersion, $state.ServicePresent, $state.DriverPresent, $stateExe $state.Installed, $stateVersion, $state.ServicePresent, $state.DriverPresent, $state.AdapterPresent, $state.DedicatedAdapterPresent, $stateExe
Write-AgentLog -FileName $logName -Message $stateSummary Write-AgentLog -FileName $logName -Message $stateSummary
if ($state.DriverPaths -and $state.DriverPaths.Count -gt 0) { if ($state.DriverPaths -and $state.DriverPaths.Count -gt 0) {
Write-AgentLog -FileName $logName -Message ("[WireGuard] Driver paths: {0}" -f ($state.DriverPaths -join '; ')) Write-AgentLog -FileName $logName -Message ("[WireGuard] Driver paths: {0}" -f ($state.DriverPaths -join '; '))
} }
if ($state.AdapterNames -and $state.AdapterNames.Count -gt 0) {
Write-AgentLog -FileName $logName -Message ("[WireGuard] Adapter names: {0}" -f ($state.AdapterNames -join '; '))
}
if ($state.DedicatedAdapterNames -and $state.DedicatedAdapterNames.Count -gt 0) {
Write-AgentLog -FileName $logName -Message ("[WireGuard] Borealis adapter names: {0}" -f ($state.DedicatedAdapterNames -join '; '))
}
if (-not ($state.Installed -and $state.DriverPresent -and $state.ServicePresent)) { if (-not ($state.Installed -and $state.DriverPresent -and $state.ServicePresent)) {
$installerCandidate = $null $installerCandidate = $null
@@ -1154,8 +1626,8 @@ AllowedIPs = 10.255.255.1/32
$state = Get-WireGuardInstallState $state = Get-WireGuardInstallState
$postVersion = if ($state.Version) { $state.Version } else { 'unknown' } $postVersion = if ($state.Version) { $state.Version } else { 'unknown' }
$postExe = if ($state.ExePath) { $state.ExePath } else { 'n/a' } $postExe = if ($state.ExePath) { $state.ExePath } else { 'n/a' }
$postSummary = "[WireGuard] Post-install state: Installed={0}; Version={1}; Service={2}; Driver={3}; Exe={4}" -f ` $postSummary = "[WireGuard] Post-install state: Installed={0}; Version={1}; Service={2}; Driver={3}; Adapter={4}; BorealisAdapter={5}; Exe={6}" -f `
$state.Installed, $postVersion, $state.ServicePresent, $state.DriverPresent, $postExe $state.Installed, $postVersion, $state.ServicePresent, $state.DriverPresent, $state.AdapterPresent, $state.DedicatedAdapterPresent, $postExe
Write-AgentLog -FileName $logName -Message $postSummary Write-AgentLog -FileName $logName -Message $postSummary
if ($state.Installed -and $state.DriverPresent) { if ($state.Installed -and $state.DriverPresent) {
Write-Host "WireGuard installed and verified (version: $($state.Version))." -ForegroundColor Green Write-Host "WireGuard installed and verified (version: $($state.Version))." -ForegroundColor Green
@@ -1163,10 +1635,14 @@ AllowedIPs = 10.255.255.1/32
Write-Host "WireGuard installed (driver pending bootstrap)." -ForegroundColor Yellow Write-Host "WireGuard installed (driver pending bootstrap)." -ForegroundColor Yellow
} }
} else { } else {
Write-Host "WireGuard already installed (version: $($state.Version))." -ForegroundColor Green if ($state.DedicatedAdapterPresent) {
Write-Host "WireGuard already installed (version: $($state.Version))." -ForegroundColor Green
} else {
Write-Host "WireGuard installed (version: $($state.Version)); provisioning Borealis adapter." -ForegroundColor Yellow
}
} }
# Ensure wintun driver is seeded even before first tunnel is created # Ensure WireGuard driver and Borealis tunnel adapter are provisioned
$wgExe = $state.ExePath $wgExe = $state.ExePath
if (-not $wgExe -or -not (Test-Path $wgExe -PathType Leaf)) { if (-not $wgExe -or -not (Test-Path $wgExe -PathType Leaf)) {
# try default install path # try default install path
@@ -1175,9 +1651,16 @@ AllowedIPs = 10.255.255.1/32
if ($wgExe -and (Test-Path $wgExe -PathType Leaf)) { if ($wgExe -and (Test-Path $wgExe -PathType Leaf)) {
try { try {
Ensure-WireGuardDriver -WireGuardExe $wgExe -LogName $logName Ensure-WireGuardDriver -WireGuardExe $wgExe -LogName $logName
$adapterOk = Ensure-WireGuardTunnelAdapter -WireGuardExe $wgExe -LogName $logName
$finalState = Get-WireGuardInstallState $finalState = Get-WireGuardInstallState
if (-not ($finalState.Installed -and $finalState.DriverPresent)) { if (-not ($finalState.Installed -and $finalState.DriverPresent)) {
throw "WireGuard driver still missing after bootstrap attempts." throw "WireGuard driver still missing after provisioning attempts."
}
if (-not $finalState.DedicatedAdapterPresent) {
throw "Borealis tunnel adapter still missing after provisioning attempts."
}
if (-not $adapterOk) {
Write-AgentLog -FileName $logName -Message "[WireGuard] Borealis tunnel adapter provisioning returned false."
} }
Write-Host "WireGuard driver verified." -ForegroundColor Green Write-Host "WireGuard driver verified." -ForegroundColor Green
} catch { } catch {

View File

@@ -22,9 +22,14 @@ import os
import subprocess import subprocess
import threading import threading
import time import time
import re
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
try:
import winreg # type: ignore
except Exception: # pragma: no cover - non-Windows guard
winreg = None
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.asymmetric import x25519
@@ -41,6 +46,9 @@ except Exception: # pragma: no cover - fallback for runtime path issues
ROLE_NAME = "WireGuardTunnel" ROLE_NAME = "WireGuardTunnel"
ROLE_CONTEXTS = ["system"] ROLE_CONTEXTS = ["system"]
TUNNEL_NAME = "Borealis"
TUNNEL_DISPLAY_NAME = "Borealis"
TUNNEL_IDLE_ADDRESS = "169.254.255.254/32"
def _log_path() -> Path: def _log_path() -> Path:
@@ -130,14 +138,21 @@ class WireGuardClient:
self.cert_root = base / "Borealis" / "Certificates" / "VPN_Client" self.cert_root = base / "Borealis" / "Certificates" / "VPN_Client"
self.temp_root = base / "Borealis" / "Temp" self.temp_root = base / "Borealis" / "Temp"
self.temp_root.mkdir(parents=True, exist_ok=True) self.temp_root.mkdir(parents=True, exist_ok=True)
self.conf_path = self.temp_root / "borealis-wg-client.conf" self.service_name = TUNNEL_NAME
self.service_name = "borealis-wg-client" self.display_name = TUNNEL_DISPLAY_NAME
self.conf_path = self._wireguard_config_path()
self.session: Optional[SessionConfig] = None self.session: Optional[SessionConfig] = None
self.idle_deadline: Optional[float] = None self.idle_deadline: Optional[float] = None
self._idle_thread: Optional[threading.Thread] = None self._idle_thread: Optional[threading.Thread] = None
self._stop_event = threading.Event() self._stop_event = threading.Event()
self._session_lock = threading.Lock()
self._client_keys = _generate_client_keys(self.cert_root) self._client_keys = _generate_client_keys(self.cert_root)
self._wg_exe = self._resolve_wireguard_exe() self._wg_exe = self._resolve_wireguard_exe()
self._last_install_already_present = False
try:
self._ensure_idle_service()
except Exception:
pass
def _resolve_wireguard_exe(self) -> str: def _resolve_wireguard_exe(self) -> str:
candidates = [ candidates = [
@@ -149,6 +164,148 @@ class WireGuardClient:
return candidate return candidate
return "wireguard.exe" return "wireguard.exe"
def _service_id(self) -> str:
return f"WireGuardTunnel${self.service_name}"
def _service_reg_path(self) -> str:
return f"SYSTEM\\CurrentControlSet\\Services\\{self._service_id()}"
def _service_reg_exists(self) -> bool:
if winreg is None:
return False
try:
winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, self._service_reg_path())
return True
except FileNotFoundError:
return False
except PermissionError:
_write_log("WireGuard service registry check denied; treating as present.")
return True
except Exception as exc:
_write_log(f"WireGuard service registry check failed: {exc}")
return False
def _service_image_path(self) -> Optional[str]:
if winreg is None:
return None
try:
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, self._service_reg_path())
value, _ = winreg.QueryValueEx(key, "ImagePath")
return str(value) if value else None
except Exception:
return None
def _service_config_path(self) -> Optional[Path]:
image_path = self._service_image_path()
if not image_path:
return None
match = re.search(r'(?i)/tunnelservice\s+"([^"]+)"', image_path)
if match:
return Path(match.group(1))
match = re.search(r"(?i)/tunnelservice\s+(\S+)", image_path)
if match:
return Path(match.group(1))
return None
def _wireguard_config_path(self) -> Path:
settings_dir = self.temp_root.parent / "Settings" / "WireGuard"
candidates = [
settings_dir,
Path(os.environ.get("ProgramFiles", "C:\\Program Files")) / "WireGuard" / "Data" / "Configurations",
Path(os.environ.get("ProgramData", "C:\\ProgramData")) / "Borealis" / "WireGuard" / "Configurations",
self.temp_root,
]
for config_dir in candidates:
candidate = config_dir / f"{self.service_name}.conf"
if candidate.is_file():
return candidate
for config_dir in candidates:
try:
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir / f"{self.service_name}.conf"
except Exception:
continue
return self.temp_root / f"{self.service_name}.conf"
def _write_config(self, text: str) -> bool:
return self._write_config_to(self.conf_path, text)
def _write_config_to(self, path: Path, text: str) -> bool:
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="ascii")
return True
except Exception as exc:
_write_log(f"Failed to write WireGuard config at {path}: {exc}")
return False
def _render_idle_config(self) -> str:
private_key = self._client_keys["private"]
return "\n".join(
[
"[Interface]",
f"PrivateKey = {private_key}",
f"Address = {TUNNEL_IDLE_ADDRESS}",
"ListenPort = 0",
]
)
def _service_exists(self) -> bool:
code, _, _ = self._run(["sc.exe", "query", self._service_id()])
if code == 0:
return True
return self._service_reg_exists()
def _install_service(self) -> bool:
code, out, err = self._run([self._wg_exe, "/installtunnelservice", str(self.conf_path)])
self._last_install_already_present = False
if code != 0:
if "already installed and running" in err.lower():
self._last_install_already_present = True
_write_log("WireGuard tunnel service already installed; skipping install.")
return True
if "access is denied" in err.lower():
_write_log("Failed to install WireGuard tunnel service: access denied; ensure agent runs elevated.")
return False
_write_log(f"Failed to install WireGuard tunnel service: code={code} err={err}")
return False
return True
def _restart_service(self) -> bool:
service_id = self._service_id()
stop_code, _, stop_err = self._run(["sc.exe", "stop", service_id])
if stop_code != 0 and stop_err:
_write_log(f"WireGuard stop service returned code={stop_code} err={stop_err}")
time.sleep(1)
start_code, _, start_err = self._run(["sc.exe", "start", service_id])
if start_code != 0 and start_err:
_write_log(f"WireGuard start service returned code={start_code} err={start_err}")
return start_code == 0
def _ensure_adapter_name(self) -> None:
if self.service_name == self.display_name:
return
args = [
"netsh.exe",
"interface",
"set",
"interface",
f'name="{self.service_name}"',
f'newname="{self.display_name}"',
]
self._run(args)
def _ensure_idle_service(self) -> None:
if self._service_exists():
return
if not Path(self._wg_exe).is_file():
return
idle_config = self._render_idle_config()
if not self._write_config(idle_config):
return
if self._install_service():
self._ensure_adapter_name()
def _validate_token(self, token: Dict[str, Any], *, signing_client: Optional[Any] = None) -> None: def _validate_token(self, token: Dict[str, Any], *, signing_client: Optional[Any] = None) -> None:
payload = dict(token or {}) payload = dict(token or {})
signature = payload.pop("signature", None) signature = payload.pop("signature", None)
@@ -232,43 +389,72 @@ class WireGuardClient:
self._idle_thread = t self._idle_thread = t
def start_session(self, session: SessionConfig, *, signing_client: Optional[Any] = None) -> None: def start_session(self, session: SessionConfig, *, signing_client: Optional[Any] = None) -> None:
if self.session: with self._session_lock:
_write_log("Rejecting start_session: existing session already active.") if self.session:
return _write_log("Rejecting start_session: existing session already active.")
return
try: try:
self._validate_token(session.token, signing_client=signing_client) self._validate_token(session.token, signing_client=signing_client)
except Exception as exc: except Exception as exc:
_write_log(f"Refusing to start WireGuard session: {exc}") _write_log(f"Refusing to start WireGuard session: {exc}")
return return
rendered = self._render_config(session) rendered = self._render_config(session)
self.conf_path.write_text(rendered, encoding="utf-8") if not self._write_config(rendered):
_write_log(f"Rendered WireGuard client config to {self.conf_path}") _write_log("Failed to write WireGuard client config.")
return
_write_log(f"Rendered WireGuard client config to {self.conf_path}")
# Pre-stop any orphaned tunnel service using the same name service_config_path = self._service_config_path()
self.stop_session(reason="preflight", ignore_missing=True) if service_config_path and service_config_path != self.conf_path:
if self._write_config_to(service_config_path, rendered):
_write_log(f"Rendered WireGuard client config to service path {service_config_path}")
code, out, err = self._run([self._wg_exe, "/installtunnelservice", str(self.conf_path)]) if not self._service_exists():
if code != 0: if not self._install_service():
_write_log(f"Failed to install WireGuard client tunnel: code={code} err={err}") return
return
self.session = session service_present = self._service_exists()
self.idle_deadline = time.time() + max(60, session.idle_seconds) if not service_present and self._last_install_already_present:
_write_log("WireGuard client session started; idle timer armed.") _write_log("WireGuard tunnel service presence inferred from install response.")
self._start_idle_monitor() service_present = True
if not service_present:
_write_log("WireGuard tunnel service still missing after install attempt.")
return
self._restart_service()
self._ensure_adapter_name()
self.session = session
self.idle_deadline = time.time() + max(60, session.idle_seconds)
_write_log("WireGuard client session started; idle timer armed.")
self._start_idle_monitor()
def stop_session(self, reason: str = "stop", ignore_missing: bool = False) -> None: def stop_session(self, reason: str = "stop", ignore_missing: bool = False) -> None:
code, out, err = self._run([self._wg_exe, "/uninstalltunnelservice", self.service_name]) with self._session_lock:
if code != 0: if not self._service_exists():
if not ignore_missing: if not ignore_missing:
_write_log(f"Failed to uninstall WireGuard client tunnel: code={code} err={err}") _write_log("WireGuard tunnel service not found when stopping session.")
else: self.session = None
_write_log(f"WireGuard client session stopped (reason={reason}).") self.idle_deadline = None
self.session = None self._stop_event.set()
self.idle_deadline = None return
self._stop_event.set()
idle_config = self._render_idle_config()
wrote_idle = self._write_config(idle_config)
service_config_path = self._service_config_path()
if service_config_path and service_config_path != self.conf_path:
wrote_idle = self._write_config_to(service_config_path, idle_config) or wrote_idle
if wrote_idle:
self._restart_service()
self._ensure_adapter_name()
_write_log(f"WireGuard client session stopped (reason={reason}).")
elif not ignore_missing:
_write_log("Failed to write idle WireGuard config.")
self.session = None
self.idle_deadline = None
self._stop_event.set()
def bump_activity(self) -> None: def bump_activity(self) -> None:
if self.session and self.idle_deadline: if self.session and self.idle_deadline:

View File

@@ -0,0 +1,106 @@
# Borealis WireGuard Troubleshooting Handoff
This file is a self-contained handoff prompt + context for a new Codex agent to resume WireGuard tunnel troubleshooting.
## Prompt to Use in a New Codex Session
Copy/paste the prompt below into a new Codex chat:
"""
You are a new Codex agent working in d:\Github\Borealis. Please do the following:
1) Read AGENTS.md, Docs/Codex/BOREALIS_AGENT.md, Docs/Codex/BOREALIS_ENGINE.md, Docs/Codex/REVERSE_TUNNELS.md, then Docs/Codex/WireGuard_Troubleshooting.md.
2) Investigate why the WireGuard tunnel does not come up (remote shell timeouts) even though the Engine emits vpn_tunnel_start.
3) Focus on the WireGuard client lifecycle in Data/Agent/Roles/role_WireGuardTunnel.py and the bootstrap logic in Borealis.ps1 (WireGuard adapter provisioning).
4) Use Data/Agent for edits (runtime under Agent/ is ephemeral). Keep the adapter name "Borealis" and ensure idempotent behavior. Do not rely on the PIA adapter.
5) Provide concrete fixes + verification steps. Be careful with Windows services and avoid GUI popup dialogs when possible.
"""
## Environment / Scope
- Workspace: d:\Github\Borealis
- Host OS: Windows 10/11 (build 26200). Current tests run on the Windows 11 machine that also runs Engine + Agent.
- Agent/Engine launch: via Borealis.ps1, always elevated as admin.
- Network: Engine + Agent run on the same host during testing (Engine endpoint is "localhost:30000").
- WireGuard version: wireguard.exe 0.5.3, wg.exe 1.0.20210914.
- PIA (Private Internet Access) is installed and supplies a wintun driver (pia-wintun.sys). Do NOT treat the PIA adapter as the Borealis adapter.
## Desired Behavior
- Agent has a dedicated WireGuard adapter named "Borealis" (Description shows "WireGuard Tunnel").
- Adapter provisioning is idempotent: if "Borealis" exists, do not recreate it.
- WireGuard config should be stored under Agent\Borealis\Settings\WireGuard\Borealis.conf (preferred) and not only in Program Files.
- Agent should bring up the WireGuard tunnel on vpn_tunnel_start, then remote shell / RDP / VNC / SSH should flow through it.
- On stop/idle, the tunnel should be torn down and firewall rules removed.
## Recent Changes (Current Repo State)
- Data/Agent/Roles/role_WireGuardTunnel.py
- Service name fix: WireGuard tunnel service is "WireGuardTunnel$Borealis".
- Config path preference: Agent\Borealis\Settings\WireGuard.
- Uses registry ImagePath to locate the actual service config when needed.
- Adds a session lock to prevent concurrent start/stop.
- Borealis.ps1
- WireGuard config search order includes Agent\Borealis\Settings\WireGuard.
- Adapter provisioning reads the service ImagePath to write config when service exists.
- Avoids /installtunnelservice if service still present to prevent GUI error dialogs.
- Adapter name is "Borealis".
Note: Data/Agent changes only apply to runtime after Borealis.ps1 re-stages the agent under Agent/.
## Symptoms from Fresh Logs (2026-01-12 19:29)
Agent (Agent/Logs/VPN_Tunnel/tunnel.log):
- "WireGuard tunnel service already installed; skipping install."
- "WireGuard tunnel service still missing after install attempt."
Engine (Engine/Logs/VPN_Tunnel/tunnel.log):
- vpn_tunnel_session_create for agent LAB-OPERATOR-01_..._SYSTEM
- WireGuard listener installed (service=borealis-wg)
- vpn_api_status_response status=up
Engine (Engine/Logs/VPN_Tunnel/remote_shell.log):
- repeated vpn_shell_connect_attempt to 10.255.0.2:47002
- timeouts
Agent (Agent/Logs/VPN_Tunnel/remote_shell.log):
- VPN shell server listening on 0.0.0.0:47002
Net effect: engine believes tunnel is "up", but remote shell cannot reach 10.255.0.2. This implies the WireGuard client tunnel is not actually up on the agent.
## Key Paths
- Agent WireGuard role: Data/Agent/Roles/role_WireGuardTunnel.py
- Agent VPN shell role: Data/Agent/Roles/role_VpnShell.py
- Engine WireGuard manager: Data/Engine/services/VPN/wireguard_server.py
- Engine tunnel service: Data/Engine/services/VPN/vpn_tunnel_service.py
- Agent tunnel logs: Agent/Logs/VPN_Tunnel/tunnel.log
- Agent shell logs: Agent/Logs/VPN_Tunnel/remote_shell.log
- Engine tunnel logs: Engine/Logs/VPN_Tunnel/tunnel.log
- Engine shell logs: Engine/Logs/VPN_Tunnel/remote_shell.log
- Agent WireGuard config: Agent/Borealis/Settings/WireGuard/Borealis.conf
## Known WireGuard Services / Names
- Engine listener service name: "borealis-wg"
- Agent tunnel service name: "WireGuardTunnel$Borealis"
- Adapter name in Control Panel: "Borealis"
## Suggested Verification Commands
- Check agent service:
- Get-Service -Name "WireGuardTunnel$Borealis"
- Get-ItemProperty "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\WireGuardTunnel$Borealis" | Select-Object ImagePath
- Confirm adapter exists:
- Get-NetAdapter -IncludeHidden | Where-Object { $_.InterfaceDescription -like "*WireGuard*" } | Select-Object Name, Status, InterfaceDescription, ifIndex
- Check WireGuard state:
- "C:\\Program Files\\WireGuard\\wg.exe" show
## Troubleshooting Focus Areas
- Ensure runtime is up-to-date (Borealis.ps1 re-staging Data/Agent -> Agent/).
- Validate service detection vs. WireGuard install output (sc.exe vs registry).
- Confirm the config file used by the service matches Agent/Borealis/Settings/WireGuard/Borealis.conf.
- Confirm /installtunnelservice is not invoked when service already exists (avoid WireGuard GUI errors).
- Confirm the WireGuard tunnel actually connects (wg.exe show handshake) before attempting remote shell.