Last active
January 16, 2025 16:49
-
-
Save Diyagi/6aeb10940e3eaa903be2c521897a5aac to your computer and use it in GitHub Desktop.
Powershell implementation of NAT-PMP made for use with ProtonVPN
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Request-NATPMPPort { | |
param ( | |
[int]$PublicPort = 1, | |
[int]$PrivatePort = 0, | |
[string]$Protocol = "udp", | |
[string]$GatewayIP = "10.2.0.1", | |
[int]$Port = 5351, | |
[int]$Lifetime = 60 | |
) | |
# UInt16 LSB Number -> Int16 MSB Byte Array | |
function PortToInt16MSBBytes { | |
param ( | |
[int]$Port | |
) | |
if ($Port -gt [UInt16]::MaxValue -or $Port -lt [UInt16]::MinValue) { | |
Write-Error "Port range is invalid." | |
exit | |
} | |
$Int16LSBPort = [BitConverter]::ToInt16([BitConverter]::GetBytes([UInt16]$Port), 0) | |
return [BitConverter]::GetBytes([System.Net.IPAddress]::HostToNetworkOrder($Int16LSBPort)) | |
} | |
# Int16 MSB Byte Array -> UInt16 LSB Number | |
function BytesToUInt16LSBPort { | |
param ( | |
[array]$PortBytes | |
) | |
$Int16LSBPort = [System.Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt16($PortBytes, 0)) | |
return [BitConverter]::ToUInt16([System.BitConverter]::GetBytes($Int16LSBPort), 0) | |
} | |
if ($Protocol.ToLower() -eq "tcp") { | |
$protocolCode = 0x01 | |
} | |
elseif ($Protocol.ToLower() -eq "udp") { | |
$protocolCode = 0x02 | |
} | |
else { | |
Write-Error "Invalid protocol, accepted values are UDP or TCP." | |
} | |
# NAT-PMP message to request a random public UDP port | |
$version = 0x00 # Version | |
$opCode = $protocolCode # Operation code for port mapping request | |
$reserved = 0x00, 0x00 # Reserved byte | |
$privatePortBytes = PortToInt16MSBBytes($PrivatePort) | |
$publicPortBytes = PortToInt16MSBBytes($PublicPort) | |
$lifetimeBytes = [BitConverter]::GetBytes([System.Net.IPAddress]::HostToNetworkOrder([BitConverter]::ToInt32([BitConverter]::GetBytes([UInt32]$Lifetime), 0))) | |
$request = @($version, $opCode) + $reserved + $privatePortBytes + $publicPortBytes + $lifetimeBytes | |
$requestBytes = [byte[]]@($request | ForEach-Object { $_ -as [byte] }) | |
$udpClient = New-Object System.Net.Sockets.UdpClient(0) | |
$udpClient.client.ReceiveTimeout = 1000 | |
$udpClient.Connect($GatewayIP, $Port) | |
[void]$udpClient.Send($requestBytes, $requestBytes.length) | |
try { | |
$udp_endpoint = New-Object System.Net.IPEndPoint([system.net.ipaddress]::Any, 0) | |
$responseBytes = $udpClient.Receive([ref] $udp_endpoint) | |
if ($responseBytes.Length -ge 16 -and $responseBytes[1] -eq $protocolCode + 0x80) { | |
$mappedPrivatePort = BytesToUInt16LSBPort($responseBytes[8..9]) | |
$mappedPublicPort = BytesToUInt16LSBPort($responseBytes[10..11]) | |
$lifetime = [System.Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($responseBytes, 12)) | |
Write-Output "Mapped public port $mappedPublicPort protocol $($Protocol.ToUpper()) to local port $mappedPrivatePort lifetime $lifetime" | |
#Write-Host ($responseBytes | ForEach-Object { "{0:X2}" -f $_ }) | |
} | |
else { | |
Write-Error "Failed to map port. Response: $([System.BitConverter]::ToString($responseBytes))" | |
return $null | |
} | |
} | |
catch { | |
Write-Error "Timeout" | |
} | |
finally { | |
$udpClient.Close() | |
} | |
} | |
while ($true) { | |
Request-NATPMPPort -PublicPort 1 -PrivatePort 0 -Protocol "UDP" -GatewayIP "10.2.0.1" -Lifetime 60 | |
Request-NATPMPPort -PublicPort 1 -PrivatePort 0 -Protocol "TCP" -GatewayIP "10.2.0.1" -Lifetime 60 | |
Start-Sleep -Seconds 45 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment