Public/Probes/Test-WslRouterReachability.ps1
|
<#
.NOTES Do not run this file directly. Dot-sourced by provision.ps1. #> # --------------------------------------------------------------------------- # Test-WslRouterReachability # Runs three probes from inside the WSL distro that the Ansible # flow uses and writes everything to a log file: # 1. ping -c 3 <RouterIp> (ICMP reachability) # 2. nc -zv -w5 <RouterIp> 22 (TCP/22 reachability) # 3. ssh -o BatchMode=yes -o ConnectTimeout=5 # <RouterIp> (SSH banner exchange) # # Why it exists: The function bakes the probes into the # orchestration so the operator gets a structured log next to # console.log + runtime-diag.log instead of a one-shot Ansible # error that hides whether the issue is the host network, the # portproxy, the router, or the workload. # # Returns a [PSCustomObject] with: # - IcmpOk [bool] ping succeeded # - TcpOk [bool] nc -z succeeded # - SshBannerOk [bool] ssh got a banner (auth may have failed # afterwards; we only care about banner) # - LogPath [string] where the per-probe transcript landed # # IcmpOk is informational only - many networks block ICMP without # blocking TCP/22, so a ping fail is not necessarily fatal. The # load-bearing field is TcpOk. SshBannerOk separates "TCP open # but ssh not listening / wrong port" from "everything happy". # # Auth is NOT exercised: BatchMode=yes prevents password prompts # so the probe is non-interactive and side-effect-free. A "Permission # denied" response counts as a successful banner exchange because # it means sshd is alive and talking. # --------------------------------------------------------------------------- function Test-WslRouterReachability { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $WslDistro, # Address WSL probes against. Typically the host-side # localhost:port forwarded by Set-RouterSshPortProxy # (127.0.0.1:2222) rather than the router's Internal-switch # IP, because WSL can only reach the latter when the # portproxy is in place. Caller decides. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $TargetAddress, [int] $TargetPort = 2222, # Where to write the per-probe transcript. Typical pattern: # next to console.log + runtime-diag.log under # <vhdPath>\diagnostics\<routerVmName>\<timestamp>\. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $LogPath ) $logDir = Split-Path -Parent $LogPath if ($logDir -and -not (Test-Path -LiteralPath $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } # Pre-flight: is the distro reachable at all? An error here # means the operator's WslDistro field points at a name that # is not installed; downstream Ansible would have failed with # an unrelated error. Invoke-WslShell -Distro $WslDistro -Command 'true' | Out-Null if ($LASTEXITCODE -ne 0) { throw "WSL distro '$WslDistro' is not reachable (wsl -d ... returned $LASTEXITCODE). Check Get-WslDistribution / wsl --list." } $segments = New-Object System.Collections.Generic.List[string] $segments.Add("=== Test-WslRouterReachability ===") $segments.Add("Target: ${TargetAddress}:${TargetPort}") $segments.Add("WSL distro: $WslDistro") $segments.Add("") # 1. ping. Informational only; ICMP block does not block TCP. $segments.Add("--- ping -c 3 $TargetAddress ---") $pingOut = Invoke-WslShell -Distro $WslDistro ` -Command "ping -c 3 -W 2 $TargetAddress 2>&1" $pingOk = $LASTEXITCODE -eq 0 $segments.Add($pingOut) $segments.Add("[exit=$LASTEXITCODE, IcmpOk=$pingOk]") $segments.Add("") # 2. nc TCP/22. Load-bearing reachability check. $segments.Add("--- nc -zv -w5 $TargetAddress $TargetPort ---") $ncOut = Invoke-WslShell -Distro $WslDistro ` -Command "nc -zv -w5 $TargetAddress $TargetPort 2>&1" $tcpOk = $LASTEXITCODE -eq 0 $segments.Add($ncOut) $segments.Add("[exit=$LASTEXITCODE, TcpOk=$tcpOk]") $segments.Add("") # 3. ssh banner. BatchMode=yes prevents prompts; "Permission # denied" counts as a successful banner exchange. $segments.Add("--- ssh banner probe @ $TargetAddress port $TargetPort ---") $sshCommand = "ssh -o BatchMode=yes -o ConnectTimeout=5 " + "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null " + "-p $TargetPort sshprobe@$TargetAddress true 2>&1" $sshOut = Invoke-WslShell -Distro $WslDistro -Command $sshCommand # Banner OK if ssh got far enough to print a banner-related line. # 'Permission denied' / 'publickey' / 'Connection closed by ...' all # mean banner+auth-stage was reached. 'Connection refused' / # 'No route to host' / 'banner exchange' / 'timed out' do NOT. $sshBannerOk = ($sshOut -match 'Permission denied|publickey|Connection closed by') -and ($sshOut -notmatch 'Connection refused|No route to host|banner exchange|timed out') $segments.Add($sshOut) $segments.Add("[SshBannerOk=$sshBannerOk]") $segments.Add("") $segments.Add("=== summary ===") $segments.Add("IcmpOk=$pingOk; TcpOk=$tcpOk; SshBannerOk=$sshBannerOk") Set-Content -LiteralPath $LogPath -Value ($segments -join "`r`n") -Encoding UTF8 [PSCustomObject]@{ IcmpOk = $pingOk TcpOk = $tcpOk SshBannerOk = $sshBannerOk LogPath = $LogPath } } |