Private/Test-PatLocalNetwork.ps1
|
function Test-PatLocalNetwork { <# .SYNOPSIS Tests if an IP address or hostname is on a local/private network. .DESCRIPTION Determines whether a given IP address falls within private IP ranges as defined by RFC 1918 (IPv4) and RFC 4193 (IPv6), or is a localhost address. Private IPv4 ranges: - 10.0.0.0/8 (10.0.0.0 - 10.255.255.255) - 172.16.0.0/12 (172.16.0.0 - 172.31.255.255) - 192.168.0.0/16 (192.168.0.0 - 192.168.255.255) - 127.0.0.0/8 (localhost) Private IPv6 ranges: - fc00::/7 (Unique Local Addresses) - ::1 (localhost) - fe80::/10 (Link-Local) .PARAMETER IPAddress The IP address to test. Can be an IPv4 or IPv6 address string. .PARAMETER Hostname A hostname to resolve and test. The function will attempt DNS resolution and test the resulting IP address(es). .OUTPUTS Boolean Returns $true if the address is on a private/local network, $false otherwise. .EXAMPLE Test-PatLocalNetwork -IPAddress "192.168.1.100" Returns: $true .EXAMPLE Test-PatLocalNetwork -IPAddress "8.8.8.8" Returns: $false .EXAMPLE Test-PatLocalNetwork -Hostname "plex.local" Returns: $true (if resolves to a private IP) .NOTES For hostnames that resolve to multiple IP addresses, returns $true if ANY of the resolved addresses is on a private network. #> [CmdletBinding(DefaultParameterSetName = 'IPAddress')] [OutputType([bool])] param( [Parameter(Mandatory = $true, ParameterSetName = 'IPAddress')] [ValidateNotNullOrEmpty()] [string] $IPAddress, [Parameter(Mandatory = $true, ParameterSetName = 'Hostname')] [ValidateNotNullOrEmpty()] [string] $Hostname ) # Helper function to test if an IP is in a private range function Test-PrivateIP { param([System.Net.IPAddress]$IP) if ($IP.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork) { # IPv4 $bytes = $IP.GetAddressBytes() # 10.0.0.0/8 if ($bytes[0] -eq 10) { return $true } # 172.16.0.0/12 (172.16.x.x - 172.31.x.x) if ($bytes[0] -eq 172 -and $bytes[1] -ge 16 -and $bytes[1] -le 31) { return $true } # 192.168.0.0/16 if ($bytes[0] -eq 192 -and $bytes[1] -eq 168) { return $true } # 127.0.0.0/8 (localhost) if ($bytes[0] -eq 127) { return $true } # 169.254.0.0/16 (link-local / APIPA) if ($bytes[0] -eq 169 -and $bytes[1] -eq 254) { return $true } return $false } elseif ($IP.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6) { # IPv6 $bytes = $IP.GetAddressBytes() # ::1 (localhost) if ($IP.ToString() -eq '::1') { return $true } # fc00::/7 (Unique Local Addresses) - first byte is fc or fd if ($bytes[0] -eq 0xfc -or $bytes[0] -eq 0xfd) { return $true } # fe80::/10 (Link-Local) - first byte is fe, second byte starts with 8, 9, a, or b if ($bytes[0] -eq 0xfe -and ($bytes[1] -band 0xc0) -eq 0x80) { return $true } return $false } return $false } if ($PSCmdlet.ParameterSetName -eq 'Hostname') { try { Write-Verbose "Resolving hostname '$Hostname' to IP address" $resolved = [System.Net.Dns]::GetHostAddresses($Hostname) if ($resolved.Count -eq 0) { Write-Verbose "No IP addresses resolved for hostname '$Hostname'" return $false } # Return true if any resolved IP is private foreach ($ip in $resolved) { if (Test-PrivateIP -IP $ip) { Write-Verbose "Hostname '$Hostname' resolves to private IP: $($ip.ToString())" return $true } } Write-Verbose "Hostname '$Hostname' resolves only to public IPs" return $false } catch { Write-Verbose "Failed to resolve hostname '$Hostname': $($_.Exception.Message)" return $false } } else { try { $ip = [System.Net.IPAddress]::Parse($IPAddress) $result = Test-PrivateIP -IP $ip Write-Verbose "IP '$IPAddress' is $(if ($result) { 'private' } else { 'public' })" return $result } catch { Write-Verbose "Failed to parse IP address '$IPAddress': $($_.Exception.Message)" return $false } } } |