Created
January 19, 2019 03:01
-
-
Save Froosh/7ff1cf3d5af583ad4231ef9f0e5b8bba to your computer and use it in GitHub Desktop.
PowerShell demo of UDP receive techniques, trying for proper async processing of packets
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
#Requires -Version 5.1 | |
Set-StrictMode -Version Latest | |
$VerbosePreference = [System.Management.Automation.ActionPreference]::Continue | |
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop | |
$UPnPReceivePort = [uint16] 1900 | |
$UPnPMulticastGroup_v4 = [System.Net.IPAddress]::Parse("239.255.255.250") | |
$UPnPMulticastGroup_v6 = [System.Net.IPAddress]::Parse("[FF02::C]") | |
#$mDNSReceivePort = [uint16] 5353 | |
#$mDNSMulticastGroup_v4 = [System.Net.IPAddress]::Parse("224.0.0.251") | |
#$mDNSMulticastGroup_v6 = [System.Net.IPAddress]::Parse("[FF02::FB]") | |
$ReceiveTimeout = New-TimeSpan -Minutes 2 | |
$LocalPort = $UPnPReceivePort | |
$LocalIP_v6 = [System.Net.IPAddress]::IPv6Any | |
$LocalEndpoint_v6 = [System.Net.IPEndPoint]::new($LocalIP_v6, $LocalPort) | |
$Socket_v6 = [System.Net.Sockets.Socket]::new( | |
[System.Net.Sockets.AddressFamily]::InterNetworkV6, | |
[System.Net.Sockets.SocketType]::Dgram, | |
[System.Net.Sockets.ProtocolType]::Udp | |
) | |
$Socket_v6.DualMode = $true | |
$Socket_v6.EnableBroadcast = $true | |
$Socket_v6.SetIPProtectionLevel( | |
[System.Net.Sockets.IPProtectionLevel]::Restricted | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::Socket, | |
[System.Net.Sockets.SocketOptionName]::ReuseAddress, | |
$true | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IP, | |
[System.Net.Sockets.SocketOptionName]::PacketInformation, | |
$true | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IPv6, | |
[System.Net.Sockets.SocketOptionName]::PacketInformation, | |
$true | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IP, | |
[System.Net.Sockets.SocketOptionName]::AddMembership, | |
[System.Net.Sockets.MulticastOption]::new($UPnPMulticastGroup_v4) | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IPv6, | |
[System.Net.Sockets.SocketOptionName]::AddMembership, | |
[System.Net.Sockets.IPv6MulticastOption]::new($UPnPMulticastGroup_v6) | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IP, | |
[System.Net.Sockets.SocketOptionName]::MulticastTimeToLive, | |
1 | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IPv6, | |
[System.Net.Sockets.SocketOptionName]::MulticastTimeToLive, | |
1 | |
) | |
$Socket_v6.Bind([System.Net.EndPoint] $LocalEndpoint_v6) | |
<# ## Sync/Blocking | |
Write-Verbose -Message "Waiting up to $ReceiveTimeout for data" | |
$Socket_v6.Blocking = $true | |
$Socket_v6.ReceiveTimeout = $ReceiveTimeout.TotalMilliseconds | |
$Buffer = [byte[]]::new($Socket_v6.ReceiveBufferSize) | |
$ReceiveEndpoint = [System.Net.IPEndPoint]::new(0,0) | |
$ReceivePacketInformation = New-Object -TypeName System.Net.Sockets.IPPacketInformation | |
$StartTime = [datetime]::Now | |
while ([datetime]::Now - $StartTime -lt $ReceiveTimeout) { | |
try { | |
Write-Verbose -Message "Timeout is: $($Socket_v6.ReceiveTimeout) milliseconds" | |
$ReceiveSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$Result = $Socket_v6.ReceiveMessageFrom($Buffer, 0, $Buffer.Length, [ref] $ReceiveSocketFlags, [ref] $ReceiveEndpoint, [ref] $ReceivePacketInformation) | |
Write-Verbose -Message "Result: $Result bytes from $($ReceiveEndpoint.Address):$($ReceiveEndpoint.Port) on interface $($ReceivePacketInformation.Interface) address $($ReceivePacketInformation.Address) with flags $($ReceiveSocketFlags.ToString())" | |
$Message = [System.Text.Encoding]::ASCII.GetString($Buffer[0..($Result-1)]) | |
Write-Verbose -Message $Message | |
} catch [System.Net.Sockets.SocketException] { | |
# Quietly eat the timeout ... or maybe do something else, break the while, etc | |
} | |
if (($NewTimeout = ($ReceiveTimeout - ([datetime]::Now - $StartTime)).TotalMilliseconds) -gt 0) { | |
$Socket_v6.ReceiveTimeout = $NewTimeout | |
} | |
} | |
Write-Verbose -Message "Final timeout, all done." | |
#> ## Sync/Blocking | |
<# ## Pseudo Async | |
Write-Verbose -Message "Waiting up to $ReceiveTimeout for data" | |
$Socket_v6.Blocking = $false | |
$Buffer = [byte[]]::new($Socket_v6.ReceiveBufferSize) | |
$ReceiveEndpoint = [System.Net.IPEndPoint]::new(0,0) | |
$ReceivePacketInformation = New-Object -TypeName System.Net.Sockets.IPPacketInformation | |
$StartTime = [datetime]::Now | |
$ReceiveSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$AsyncResult = $Socket_v6.BeginReceiveMessageFrom($Buffer, 0, $Buffer.Length, $ReceiveSocketFlags, [ref] $ReceiveEndpoint, $null, $null) | |
while ([datetime]::Now - $StartTime -lt $ReceiveTimeout) { | |
if ($AsyncResult.IsCompleted) { | |
$Result = $Socket_v6.EndReceiveMessageFrom($AsyncResult, [ref] $ReceiveSocketFlags, [ref] $ReceiveEndpoint, [ref] $ReceivePacketInformation) | |
Write-Verbose -Message "Result: $Result bytes from $($ReceiveEndpoint.Address):$($ReceiveEndpoint.Port) on interface $($ReceivePacketInformation.Interface) address $($ReceivePacketInformation.Address) with flags $($ReceiveSocketFlags.ToString())" | |
$Message = [System.Text.Encoding]::ASCII.GetString($Buffer[0..($Result-1)]) | |
Write-Verbose -Message $Message | |
$ReceiveSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$AsyncResult = $Socket_v6.BeginReceiveMessageFrom($Buffer, 0, $Buffer.Length, $ReceiveSocketFlags, [ref] $ReceiveEndpoint, $null, $null) | |
} else { | |
Start-Sleep -Milliseconds 100 | |
} | |
} | |
#> ## Pseudo Async | |
<# ## Not Quite Working Async ['Native' AsyncCallback] | |
$OnReceiveData = [System.AsyncCallback] { | |
Param ( | |
[System.IAsyncResult] $IAsyncResult | |
) | |
$CallbackState = [hashtable] $IAsyncResult.AsyncState | |
$CallbackSocket = $CallbackState.Socket | |
$CallbackBuffer = $CallbackState.Buffer | |
$CallbackSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$CallbackEndpoint = [System.Net.IPEndPoint]::new(0, 0) | |
$CallbackPacketInformation = New-Object -TypeName System.Net.Sockets.IPPacketInformation | |
$CallbackResult = $CallbackSocket.EndReceiveMessageFrom($IAsyncResult, [ref] $CallbackSocketFlags, [ref] $CallbackEndpoint, [ref] $CallbackPacketInformation) | |
$CallbackResultData = @{ | |
Result = $CallbackResult | |
Endpoint = $CallbackEndpoint | |
SocketFlags = $CallbackSocketFlags | |
PacketInformation = $CallbackPacketInformation | |
Data = $CallbackBuffer[0..($CallbackResult - 1)] | |
} | |
$CallbackState.Queue.Enqueue($CallbackResultData) | |
$CallbackSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$null = $CallbackSocket.BeginReceiveMessageFrom($CallbackBuffer, 0, $CallbackBuffer.Length, $CallbackSocketFlags, [ref] $CallbackEndpoint, $CallbackState.Callback, $CallbackState) | |
} | |
Write-Verbose -Message "Waiting up to $ReceiveTimeout for data" | |
$Socket_v6.Blocking = $false | |
$Buffer = [byte[]]::new($Socket_v6.ReceiveBufferSize) | |
$AsyncQueue = [System.Collections.Queue]::Synchronized([System.Collections.Queue]::new()) | |
$AsyncState = @{ | |
Socket = $Socket_v6 | |
Buffer = $Buffer | |
Queue = $AsyncQueue | |
Callback = $OnReceiveData | |
} | |
$StartTime = [datetime]::Now | |
$ReceiveSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$ReceiveEndpoint = [System.Net.IPEndPoint]::new(0, 0) | |
$null = $Socket_v6.BeginReceiveMessageFrom($Buffer, 0, $Buffer.Length, $ReceiveSocketFlags, [ref] $ReceiveEndpoint, $AsyncState.Callback, $AsyncState) | |
while (([datetime]::Now - $StartTime) -lt $ReceiveTimeout) { | |
while ($AsyncQueue.Count -gt 0) { | |
$ResultData = $AsyncQueue.Dequeue() | |
Write-Verbose -Message "Result: $($ResultData.Result) bytes from $($ResultData.Endpoint.Address):$($ResultData.Endpoint.Port) on interface $($ResultData.PacketInformation.Interface) address $($ResultData.PacketInformation.Address) with flags $($ResultData.SocketFlags.ToString())" | |
$Message = [System.Text.Encoding]::ASCII.GetString($ResultData.Data) | |
Write-Verbose -Message $Message | |
} | |
Start-Sleep -Milliseconds 100 | |
} | |
#> ## Not Quite Working Async ['Native' AsyncCallback] | |
## Full Async [Events] | |
$CallbackEventBridge = @" | |
using System; | |
public sealed class CallbackEventBridge { | |
public event AsyncCallback CallbackComplete = delegate { }; | |
private CallbackEventBridge() {} | |
private void CallbackInternal(IAsyncResult result) { | |
CallbackComplete(result); | |
} | |
public AsyncCallback Callback { | |
get { return new AsyncCallback(CallbackInternal); } | |
} | |
public static CallbackEventBridge Create() { | |
return new CallbackEventBridge(); | |
} | |
} | |
"@ | |
# Add the event bridge type if not already defined | |
if (-not ("CallbackEventBridge" -as [type])) { | |
Add-Type -TypeDefinition $CallbackEventBridge | |
} | |
$OnReceiveData = { | |
Param ( | |
[System.IAsyncResult] $IAsyncResult | |
) | |
$CallbackState = [hashtable] $IAsyncResult.AsyncState | |
$CallbackSocket = $CallbackState.Socket | |
$CallbackBuffer = $CallbackState.Buffer | |
$CallbackSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$CallbackEndpoint = [System.Net.IPEndPoint]::new(0, 0) | |
$CallbackPacketInformation = New-Object -TypeName System.Net.Sockets.IPPacketInformation | |
$CallbackResult = $CallbackSocket.EndReceiveMessageFrom($IAsyncResult, [ref] $CallbackSocketFlags, [ref] $CallbackEndpoint, [ref] $CallbackPacketInformation) | |
$CallbackResultData = @{ | |
Result = $CallbackResult | |
Endpoint = $CallbackEndpoint | |
SocketFlags = $CallbackSocketFlags | |
PacketInformation = $CallbackPacketInformation | |
Data = $CallbackBuffer[0..($CallbackResult - 1)] | |
} | |
$CallbackState.Queue.Enqueue($CallbackResultData) | |
$CallbackSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$CallbackState.CurrentIAsyncResult = $CallbackSocket.BeginReceiveMessageFrom($CallbackBuffer, 0, $CallbackBuffer.Length, $CallbackSocketFlags, [ref] $CallbackEndpoint, $CallbackState.Callback, $CallbackState) | |
} | |
Write-Verbose -Message "Waiting up to $ReceiveTimeout for data" | |
$Socket_v6.Blocking = $false | |
$Buffer = [byte[]]::new($Socket_v6.ReceiveBufferSize) | |
$AsyncBridge = [CallbackEventBridge]::Create() | |
$AsyncQueue = [System.Collections.Queue]::Synchronized([System.Collections.Queue]::new()) | |
$AsyncState = [hashtable]::Synchronized(@{ | |
Socket = $Socket_v6 | |
Buffer = $Buffer | |
Queue = $AsyncQueue | |
Callback = $AsyncBridge.Callback | |
CurrentIAsyncResult = $null | |
}) | |
$ObjectEventSubscriber = Register-ObjectEvent -InputObject $AsyncBridge -EventName CallbackComplete -Action $OnReceiveData -MessageData $Args | |
$StartTime = [datetime]::Now | |
$ReceiveSocketFlags = [System.Net.Sockets.SocketFlags]::None | |
$ReceiveEndpoint = [System.Net.IPEndPoint]::new(0, 0) | |
$AsyncState.CurrentIAsyncResult = $Socket_v6.BeginReceiveMessageFrom($Buffer, 0, $Buffer.Length, $ReceiveSocketFlags, [ref] $ReceiveEndpoint, $AsyncState.Callback, $AsyncState) | |
while (([datetime]::Now - $StartTime) -lt $ReceiveTimeout) { | |
while ($AsyncQueue.Count -gt 0) { | |
$ResultData = $AsyncQueue.Dequeue() | |
Write-Verbose -Message "Result: $($ResultData.Result) bytes from $($ResultData.Endpoint.Address):$($ResultData.Endpoint.Port) on interface $($ResultData.PacketInformation.Interface) address $($ResultData.PacketInformation.Address) with flags $($ResultData.SocketFlags.ToString())" | |
$Message = [System.Text.Encoding]::UTF8.GetString($ResultData.Data) | |
Write-Verbose -Message $Message | |
} | |
Start-Sleep -Milliseconds 100 | |
} | |
Unregister-Event -SourceIdentifier $ObjectEventSubscriber.Name -Force | |
#> ## Full Async [Events] | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IP, | |
[System.Net.Sockets.SocketOptionName]::DropMembership, | |
[System.Net.Sockets.MulticastOption]::new($UPnPMulticastGroup_v4) | |
) | |
$Socket_v6.SetSocketOption( | |
[System.Net.Sockets.SocketOptionLevel]::IPv6, | |
[System.Net.Sockets.SocketOptionName]::DropMembership, | |
[System.Net.Sockets.IPv6MulticastOption]::new($UPnPMulticastGroup_v6) | |
) | |
$Socket_v6.Shutdown([System.Net.Sockets.SocketShutdown]::Both) | |
$Socket_v6.Close() | |
$Socket_v6.Dispose() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment