Skip to content

Instantly share code, notes, and snippets.

@Froosh
Created January 19, 2019 03:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Froosh/7ff1cf3d5af583ad4231ef9f0e5b8bba to your computer and use it in GitHub Desktop.
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
#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