param( [string]$HostLanIp = "192.168.178.58", [string]$FritzBoxIp = "192.168.178.1", [ValidateSet("LanPreflight", "Guest")] [string]$Mode = "LanPreflight", [string]$ReportPath = "" ) $ErrorActionPreference = "Stop" function Test-TcpPort { param( [string]$RemoteHost, [int]$Port, [int]$TimeoutMs = 1500 ) $client = [System.Net.Sockets.TcpClient]::new() try { $async = $client.BeginConnect($RemoteHost, $Port, $null, $null) $ok = $async.AsyncWaitHandle.WaitOne($TimeoutMs, $false) if (-not $ok) { return $false } $client.EndConnect($async) return $true } catch { return $false } finally { $client.Close() } } function Add-Result { param( [System.Collections.Generic.List[object]]$Results, [string]$Name, [string]$Target, [bool]$Reachable, [string]$ExpectedGuest, [string]$Risk ) $Results.Add([pscustomobject]@{ Name = $Name Target = $Target Reachable = $Reachable ExpectedFromGuest = $ExpectedGuest RiskIfReachableFromGuest = $Risk }) } $adapters = Get-NetIPConfiguration | Where-Object { $_.IPv4Address -and $_.NetAdapter.Status -eq "Up" } | Select-Object InterfaceAlias, @{Name="IPv4";Expression={$_.IPv4Address.IPAddress -join ", "}}, @{Name="Gateway";Expression={$_.IPv4DefaultGateway.NextHop -join ", "}}, @{Name="DnsServer";Expression={$_.DNSServer.ServerAddresses -join ", "}} $results = [System.Collections.Generic.List[object]]::new() Add-Result $results "Unraid HTTP/LAN" "${HostLanIp}:80" (Test-TcpPort $HostLanIp 80) "blocked" "Guest can reach LAN web entrypoint directly" Add-Result $results "Unraid HTTPS/LAN" "${HostLanIp}:443" (Test-TcpPort $HostLanIp 443) "blocked" "Guest can reach LAN HTTPS entrypoint directly" Add-Result $results "Gitea SSH/LAN" "${HostLanIp}:222" (Test-TcpPort $HostLanIp 222) "blocked" "Guest can reach Git SSH" Add-Result $results "AdGuard Admin/LAN" "${HostLanIp}:8082" (Test-TcpPort $HostLanIp 8082) "blocked" "Guest can reach AdGuard admin UI" Add-Result $results "InfluxDB LAN" "${HostLanIp}:8181" (Test-TcpPort $HostLanIp 8181) "blocked" "Guest can reach InfluxDB writer endpoint" Add-Result $results "FRITZ!Box LAN UI" "${FritzBoxIp}:80" (Test-TcpPort $FritzBoxIp 80) "blocked-or-guest-gateway-only" "Guest can reach main router UI" $risk = if ($Mode -eq "Guest") { $results | Where-Object { $_.ExpectedFromGuest -like "blocked*" -and $_.Reachable } } else { $results | Where-Object { $_.Name -in @("AdGuard Admin/LAN", "InfluxDB LAN") -and $_.Reachable } } $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $lines = [System.Collections.Generic.List[string]]::new() $lines.Add("# Guest/IoT Isolation Check") $lines.Add("") $lines.Add("Timestamp: $timestamp") $lines.Add("Mode: $Mode") $lines.Add("Host LAN IP: $HostLanIp") $lines.Add("FRITZ!Box IP: $FritzBoxIp") $lines.Add("Risk count: $($risk.Count)") $lines.Add("") $lines.Add("## Active Network Adapters") $lines.Add("") $lines.Add("| Interface | IPv4 | Gateway | DNS |") $lines.Add("|---|---|---|---|") foreach ($adapter in $adapters) { $lines.Add("| $($adapter.InterfaceAlias) | $($adapter.IPv4) | $($adapter.Gateway) | $($adapter.DnsServer) |") } $lines.Add("") $lines.Add("## Port Tests") $lines.Add("") $lines.Add("| Name | Target | Reachable | Expected from guest Wi-Fi | Risk if reachable from guest |") $lines.Add("|---|---|---:|---|---|") foreach ($result in $results) { $lines.Add("| $($result.Name) | $($result.Target) | $($result.Reachable) | $($result.ExpectedFromGuest) | $($result.RiskIfReachableFromGuest) |") } $lines.Add("") $lines.Add("## Interpretation") $lines.Add("") $lines.Add("- `LanPreflight`: reachable `80/443/222` can be normal; `8082` and `8181` should still be blocked.") $lines.Add("- `Guest`: all listed LAN targets should be blocked. Public domains may still work via the internet path.") $lines.Add("- A non-zero risk count means the selected mode failed.") $text = $lines -join [Environment]::NewLine if ($ReportPath) { $parent = Split-Path -Parent $ReportPath if ($parent) { New-Item -ItemType Directory -Force -Path $parent | Out-Null } Set-Content -Path $ReportPath -Value $text -Encoding UTF8 } Write-Output $text if ($risk.Count -gt 0) { exit 2 } exit 0