Private/Test-WinClatIPv4Target.ps1

Function Test-WinClatIPv4Target {

    <#

    .SYNOPSIS
        Validates the Test-WinClat -IPv4Target value.

    .DESCRIPTION
        [System.Net.IPAddress]::Parse() alone is insufficient: it accepts shortened forms ('1' parses as 0.0.0.1) and IPv6 addresses. Require all of (DESIGN.md, section 8.5):
          1. A strict dotted-quad pattern (four octets, each 0 to 255, no leading zeros).
          2. AddressFamily of InterNetwork after parsing.
          3. Not loopback (127.0.0.0/8), unspecified (0.0.0.0), broadcast (255.255.255.255), multicast (224.0.0.0/4), reserved (240.0.0.0/4), or 192.0.0.0/29.

        Leading zeros are rejected because Windows PowerShell 5.1 parses them as octal ('010' is 8) while PowerShell 7 may parse them as decimal.

        Throws a descriptive error when invalid, so ValidateScript shows a useful message. Returns $true when valid.

    .PARAMETER Address
        The IPv4 address to validate, for example '192.0.2.10'.

    .INPUTS
        None. This function does not accept pipeline input.

    .OUTPUTS
        System.Boolean. Always $true; an invalid address throws instead.

    .EXAMPLE
        Test-WinClatIPv4Target -Address '192.0.2.10'

        Returns $true.

    .EXAMPLE
        Test-WinClatIPv4Target -Address '10.1'

        Throws because the value is not a dotted-quad IPv4 address.

    .LINK
        https://learn.microsoft.com/dotnet/api/system.net.ipaddress

    .NOTES
        Version: 1.0.0
        Creation Date: October 28, 2026
        Last Updated: October 28, 2026
        Author: Richard Hicks
        Organization: Richard M. Hicks Consulting, Inc.
        Contact: rich@richardhicks.com
        Website: https://www.richardhicks.com/

    #>


    [CmdletBinding()]
    [OutputType([bool])]

    Param (

        [Parameter(Mandatory)]
        [string]$Address

    )

    $Octet = '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'

    If ($Address -notmatch "^$Octet(\.$Octet){3}$") {

        Throw "'$Address' is not a valid IPv4 address. Use dotted-quad notation with four octets from 0 to 255 and no leading zeros, for example 192.0.2.10."

    }

    $Parsed = $null

    If (-not [System.Net.IPAddress]::TryParse($Address, [ref]$Parsed) -or $Parsed.AddressFamily -ne [System.Net.Sockets.AddressFamily]::InterNetwork) {

        Throw "'$Address' is not a valid IPv4 address."

    }

    $Bytes = $Parsed.GetAddressBytes()

    If ($Bytes[0] -eq 127) {

        Throw "'$Address' is a loopback address (127.0.0.0/8). Use an address reachable through the NAT64 gateway."

    }

    If ($Bytes[0] -eq 0 -and $Bytes[1] -eq 0 -and $Bytes[2] -eq 0 -and $Bytes[3] -eq 0) {

        Throw "'$Address' is the unspecified address. Use an address reachable through the NAT64 gateway."

    }

    If ($Bytes[0] -eq 255 -and $Bytes[1] -eq 255 -and $Bytes[2] -eq 255 -and $Bytes[3] -eq 255) {

        Throw "'$Address' is the broadcast address. Use a unicast address reachable through the NAT64 gateway."

    }

    If ($Bytes[0] -ge 224 -and $Bytes[0] -le 239) {

        Throw "'$Address' is a multicast address (224.0.0.0/4). Use a unicast address reachable through the NAT64 gateway."

    }

    If ($Bytes[0] -ge 240) {

        Throw "'$Address' is in the reserved range (240.0.0.0/4). Use a unicast address reachable through the NAT64 gateway."

    }

    If ($Bytes[0] -eq 192 -and $Bytes[1] -eq 0 -and $Bytes[2] -eq 0 -and $Bytes[3] -le 7) {

        Throw "'$Address' is in the IPv4 Service Continuity Prefix (192.0.0.0/29) used by CLAT itself. Use an address reachable through the NAT64 gateway."

    }

    Return $true

}