Get-NatType.psm1




function Get-NatType{


# Define Teredo server information
$TeredoServerName = "win8.ipv6.microsoft.com"
$TeredoServerPort = 3544
$OtherPort = 3543

# Define test pass results
[uint32] $PrimaryExternalPort = 0xbadbeef
[uint32] $SecondaryExternalPort = 0xbadbeef
$TestPrimary = $false
$TestCone = $false
$TestAddressRestricted = $false
$TestSymmetric = $false

# Define RS packets
[Byte[]] $Rs     = 0x00, 0x01, 0x00, 0x00, 0xBB, 0x1B, 0x5F, 0xC5, 0x1B, 0x66, 0x50, 0x57, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3A, 0xFF, 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x85, 0x00, 0x7D, 0x38, 0x00, 0x00, 0x00, 0x00
[Byte[]] $RsCone = 0x00, 0x01, 0x00, 0x00, 0xBB, 0x1B, 0x5F, 0xC5, 0x1B, 0x66, 0x50, 0x57, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3A, 0xFF, 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x85, 0x00, 0xFD, 0x37, 0x00, 0x00, 0x00, 0x00


# Helper function to extract the external port from the OriginHeader
# RaPacket is the contents of the IPv4 UDP payload
function Get-ExternalPort([Byte[]] $RaPacket)
{
    # The OriginPort is 15 bytes into the RaPacket and two bytes long
    # The RA packet must be 109 bytes long
    if ($RaPacket.Count -ne 109)
    {
        Write-Error "Cannot parse RA packet of unexpected length " $RaPacket.Count
        exit
    }

    # The ports are obfusticated
    $byte1 = $RaPacket[15] -bxor 0xff
    $byte2 = $RaPacket[16] -bxor 0xff

    [int] $port = $byte1 * 256 + $byte2
    return $port
}

$OutputObject = New-Object -TypeName PsObject


# Get the primary Teredo Server Address
$primaryAddress = (Resolve-DnsName -Name $TeredoServerName).IPAddress
Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "PrimaryTeredoServerIP" -Value $primaryAddress


# Get the secondary address
$addressParts = $primaryAddress.Split(".")
$lastByte = [int]$addressParts[3]
$lastByte += 1
$addressParts[3] = [string] $lastByte
$secondaryAddress = "{0}.{1}.{2}.{3}" -f $addressParts[0], $addressParts[1], $addressParts[2], $addressParts[3]
Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "SecondaryTeredoServerIP" -Value $SecondaryAddress

# We don't save the IP endpoints so create one and reuse it
$remoteEp = New-Object System.Net.IPEndPoint(0,0)

# Create IP Endpoint objects for all send destinations
$primaryEp = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Parse($primaryAddress), $TeredoServerPort)
$secondaryEp = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Parse($secondaryAddress), $TeredoServerPort)
$otherEp = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Parse($primaryAddress), $OtherPort)

# Create IPv4 UDP sockets for communication with the primary and secondary server IPs
$primarySocket = New-Object System.Net.Sockets.UdpClient
$primarySocket.Client.ReceiveTimeout = 3000 # 3 seconds

$secondarySocket = New-Object System.Net.Sockets.UdpClient
$secondarySocket.Client.ReceiveTimeout = 3000 # 3 seconds

# Perform primary RS RA Test
try
{
    $remoteEp = New-Object System.Net.IPEndPoint(0,0)
    $bytesSent = $primarySocket.Send($Rs, $Rs.Count, $primaryEp)
    $response = $primarySocket.Receive([ref] $remoteEp)
    $TestPrimary = $true
    $PrimaryExternalPort = Get-ExternalPort -RaPacket $response
    
    Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "PrimaryExternalPort" -Value  $PrimaryExternalPort
}
catch
{
    Write-Error "Teredo could not qualify on this network - Primary RS/RA blocked. Please check ACL for UDP port 3544"
    exit
}

# Perform Cone NAT Test
# Transmit an RS from the primary socket with the cone bit set
# If we receive an RA on the secondary socket then this is a CONE NAT
try
{
    $remoteEp = New-Object System.Net.IPEndPoint(0,0)
    $bytesSent = $primarySocket.Send($RsCone, $RsCone.Count, $primaryEp)
    $response = $primarySocket.Receive([ref] $remoteEp)
    Write-Host "Received Cone RA"
    $TestCone = $true
    
    Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "ReceiveConeRA" -Value  $True
}
catch
{
    Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "ReceiveConeRA" -Value  $False
    Write-Warning "Did not receive cone RA. This is a non-fatal test failure. If you'd like to simulate a Cone NAT, please add your computer to the router's DMZ"
}

# Perform Address Restricted NAT test
# Transmit an RS from the secondary socket to a Non-Teredo port ($OtherPort)
# Transmit an RS from the primary socket with the cone bit set
# If we receive the RA on the secondary socket then this is an Address Restricted NAT
try
{
    $remoteEp = New-Object System.Net.IPEndPoint(0,0)
    # Swap the port on the secondary socket
    $bytesSent = $secondarySocket.Send($Rs, $Rs.Count, $otherEp)
    # Wait 1 second to ensure in-order packet transmission
    Sleep -Seconds 1
    $bytesSent = $primarySocket.Send($RsCone, $RsCone.Count, $primaryEp)
    $response = $primarySocket.Receive([ref] $remoteEp)
    Write-Debug "Received Address-Restricted RA"
    $TestAddressRestricted = $true
    
    Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "ReceiveAddressRestrictedRA" -Value  $True

}
catch
{
    Write-warning "Did not receive address restricted RA. This is a non-fatal test failure."
    Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "ReceiveAddressRestrictedRA" -Value  $False
 
}


Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "NATType" -Value  $null

# Perform symmetric NAT test
# Transmit an RS from the secondary socket
# Compare the external NAT port from the RS / RA exchange to the one from the primary RS RA Test

# REVIEW: Does not detect address symmetric
try
{
    $remoteEp = New-Object System.Net.IPEndPoint(0,0)
    $bytesSent = $PrimarySocket.Send($Rs, $Rs.Count, $secondaryEp)
    $response = $PrimarySocket.Receive([ref] $remoteEp)
    $SecondaryExternalPort = Get-ExternalPort -RaPacket $response
    $TestSymmetric = $PrimaryExternalPort -ne $SecondaryExternalPort
    Write-Debug "Received secondary RA with external port $SecondaryExternalPort" 

    
    Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name "SecondaryRAExternalPort" -Value  $SecondaryExternalPort

}
catch
{
    Write-Error "Teredo could not qualify on this network - Secondary RS / RA blocked. Please check ACL for UDP port 3544"
    
    $OutputObject.NATType =  "Broken"

}



# Write the results
if ($TestCone)
{
    Write-Debug "NAT TYPE: Cone. Status: Green"
    $OutputObject.NATType =  "Cone"

}

if ($TestAddressRestricted -and -not $TestCone)
{
    Write-Debug "NAT TYPE: Address Restricted. Status: Green"
    $OutputObject.NATType =  "AddressRestricted"

}

if (-not $TestAddressRestricted -and -not $TestSymmetric)
{
    Write-Debug "NAT TYPE: Port Restricted. Status: Yellow"
    $OutputObject.NATType =  "PortRestricted"
}

if ($TestSymmetric)
{
    Write-Warning "Symmetric NAT detected"
    Write-Debug "NAT TYPE: Symmetric. Status: Red"
    $OutputObject.NATType =  "Symmetric"
}

$OutputObject
}


export-modulemember -function Get-NatType