PowerShell und UDP

Als Vorarbeit für VoIP-Tests wie End2EndVOIP etc. habe ich mal versucht per PowerShell einfache UDP-Pakete zu senden.

UDP-Paket senden

Es dauert nur wenige Minuten den Code zum Senden eines UDP-Pakets fertig zu haben. Hier ein Auszug des Skripts:

# send-udp
#
# Simple Tool to send UDP-Ports to a range with a various source port range.
# you can use Netmon/WireShark on the target to see the incomping traffic.
# i have NO listener to listen on all ports
#
#
# Links: http://pshscripts.blogspot.de/2008/12/send-udpdatagramps1.html

param (
   [string]$remoteip = "192.168.100.1", # IP to send to 
   [int]$remoteudpport=50000,           # port to send to
   [int]$sourceudpport = 0,             # SourcePort, maybe empty uses and available port
   [string]$buffer = "SendUDP Message by msxfaq"
)

$udpClient = new-Object system.Net.Sockets.Udpclient($sourceport) 
$byteBuffer  = [System.Text.Encoding]::ASCII.GetBytes($Buffer)
$sendbytes = $udpClient.Send($byteBuffer, $byteBuffer.length, $remoteip, $remoteudpport)
if ($sendbytes -ne $byteBuffer.length) {
   write-host "Mismatch bytes"
}

PowerShell und .NET ist schon genial, wie man mit ganz wenigen Zeilen selbst solche Basisfunktionen umsetzen kann. Und wenn Sie die Parameter entsprechend anpassen, können Sie damit schon ein Netzwerk fluten.

Hier das komplette Skript als Muster:

send-udp.1.0.ps1

Es ist nicht möglich, auf dieser Ebene auch noch QoS-Tags, also DSCP-Tags zu setzen. Das können Sie aber einfach mit Windows Bordmitteln machen. Es gibt aber auch noch die Socket-Schnittstelle, die ebenfalls verwenden werden kann. Sie ist aber etwas umfangreicher. Hier ein Codeauszug:

$IPAddress = [system.net.IPAddress]::Parse($targetip) 
$AddressFamily = [System.Net.Sockets.AddressFamily]::InterNetwork 
$SockettypeDatagram = [System.Net.Sockets.SocketType]::Dgram 
$ProtocolTypeUDP = [System.Net.Sockets.ProtocolType]::UDP 
$Buffer = [System.Text.Encoding]::ASCII.GetBytes("UDPTest MSXFAQ") 

$Sock     = New-Object System.Net.Sockets.Socket $AddressFamily, $SockettypeDatagram, $ProtocolTypeUDP
$Sock.TTL = 100  
$remotesystem = New-Object System.Net.IPEndPoint $IPAddress, $port
$sock.Connect(remotesystem)  
$Sent = $Sock.Send($Buffer)

System.Net.Sockets.Socket
http://msdn.microsoft.com/de-de/library/vstudio/system.net.sockets.socket

Qualitätskontrolle mit NetMon

Der einfaches Weg die Funktion zu verifizieren  ist ein Mitschnitt durch NetMon. Hier habe ich über eine WiFi-Verbindung das Paket einmal versendet.

Der Code funktioniert und der ausführende Prozess muss dabei nicht einmal Administrator sein.

UDP und ICMP not Reachable

Im Gegensatz zu TCP gib es bei UDP keinen Handshake mit Retransmit. Die Übertragung von Daten per UDP ist daher ungesichert gegen Verluste. Senden Sie also nur unkritische Daten, deren Zustellung nicht erforderlich ist.

Zudem habe ich noch nie gesehen, das eine Gegenstelle. die auf dem Port nicht erreichbar ist ein "ICMP not reachable" zurück sendet. Ein UDP-Paket ist also eher wie eine Flaschenpost. Sie können prüfen, ob sie versendet wurde, aber solange niemand darauf antwortet, können Sie den Status der Zustellung nicht können.

Zeitverhalten

Für ein später geplantes VoIP-Monitoring muss ich aber mehrere Pakete senden. Das geht per For-Schleife ehr einfach aber wichtig ist dabei, dass die Pakete wirklich mit dem gleichen Zeitabstand und in der geplanten Reihenfolge versendet werden. Also habe ich eine FOR-Schleife drum herum gebaut.

for ($i=1; $i -lt $repeat; $i++) {
   $byteBuffer  = [System.Text.Encoding]::ASCII.GetBytes(($Buffer + [string]($i)))
   $sentbytes = $udpClient.Send($byteBuffer, $byteBuffer.length, $remoteip, $remoteudpport+$i)
   if ($sentbytes -ne $byteBuffer.length) {
      write-host "Send Bytes Mismatch"
   }
   start-sleep -milliseconds $throttle
}

Abweichend zum normalen Code habe ich hier den "RemoteUDPPort" mit dem Counter erhöht, um im WireShark gleich anhand des Ports die "Reihenfolge" zusehen.

Und hier ist zu sehen, dass die Pakete in ca.10-11ms-Abständen gesendet werden. Eine gewisse Streuung liefert also schon der Sender. Ob das nun an PowerShell, Windows oder vielleicht an einem anderen Paket liegt, das gerade versendet wird und daher mein Paket etwas warten muss, ist nicht bestimmbar. Ich habe den Test per WiFi (135MBit) gemacht, was sicher auch noch etwas Varianz mit rein bringt.

UDP empfangen

Als nächstes steht die Aufgabe an, eine UDP-Nachricht zu empfangen. Auch das geht mit PowerShell und NET-Bordmitteln. Ein Auszug aus dem Source zeigt die Basisfunktion, um auf einem Port zu lauschen und die empfangenen Pakete zurück zu geben:

# receive-udp

param (
	[string]$localip = "0.0.0.0",
	[string]$udplistenport=1514
)

$udpClient = New-Object system.Net.Sockets.Udpclient($udplistenport)
$RemoteIpEndPoint = New-Object System.Net.IPEndPoint([system.net.IPAddress]::Parse($localip)  , $udplistenport);

while ($true) {
	Write-host "Receive-UDP:Wait für Data on Port: $udplistenport"
	$data=$udpclient.receive([ref]$RemoteIpEndPoint)
	
	write-host "Received packet from IP " $RemoteIpEndPoint.address ":" $RemoteIpEndPoint.Port
	write-host "Content" ([string]::join("",([System.Text.Encoding]::ASCII.GetChars($data))))
}

Knifflig ist, dass Windows selbst keinen Zeitstempel anfügt aber die Pakete puffert. Die Funktion kommt zurück, wenn ein Paket angekommen ist oder schon eins im Puffer ist. Wer also möglichst genau den Zeitpunkt des Empfangs benötigt, muss sehr zeitnah die Daten auslesen. Wenn ich auf dem gleichen PC die Daten per "localhost" sende und empfange, zeigen sich schon größere unterschiede. Wobei hier sicher auch die Bildschirmausgabe einen Einfluss haben kann.

PS C:\> .\receive-udp.ps1 | select timestamp
Receive-UDP:Wait für Data on Port: 50002

timestamp
---------
2013.10.24 14:25:31.500
2013.10.24 14:25:31.521
2013.10.24 14:25:31.530
2013.10.24 14:25:31.550
2013.10.24 14:25:31.570
2013.10.24 14:25:31.580
2013.10.24 14:25:31.600
2013.10.24 14:25:31.610
2013.10.24 14:25:31.630
2013.10.24 14:25:31.640
Receive-UDP:Closing

Es sind alle 10 gesendeten Pakete angekommen aber die Abstände sind doch zumindest schwankend. Ich gehe mal davon aus, dass sie relativ linear gesendet wurden. Hier das komplette Skript als Muster:

receive-udp.1.0.ps1

UDP Echo

Kombiniert man nun den UDP-Empfänger und den UDP-Sender, dann lässt sich ein ECHO-System allein mit PowerShell realisieren. Dieses Skript wartet auf Pakete auf dem angegebenen Port und sendet diese direkte zurück.

echo-udp.1.0.ps1

Achtung
Dies ist ein klassischer ECHO-Server der durchaus auch missbrauch werden kann. Bei UDP gibt es keinen Handshake und so können falsche Absenderadressen nicht erkannt werden. Ich kann also eine dritte Station mit UDP-Paketen fluten, in dem ich an so einen ECHO-Server ein Paket sende und die Absenderadresse (IP) fälsche

Durchsatz

Bleibt zuletzt noch die Frage, wie schnell ein PowerShell-Script UDP-Pakete senden kann. Dazu muss man aber bedenken, dass UDP kein gesichertes Protokoll wie TCP ist. Es gibt keine "Connections" und verlorene Pakete werden nicht vom IP-Stack erneut gesendet. für Voice und Video ist dies ideal ebenso wie für Meldungen wie SNMP und SYSLOG, die nicht jedes mal einen TCP-Handshake durchführen wollen. Insofern verbietet sich schon die Übertragung größerer Datenmengen da sie ansonsten selbst den Mehrwert von TCP selbst implementieren müssten.

Dennoch ist es natürlich schon interessant, wie schnell ein PC mit PowerShell UDP-Pakete zu einem anderen PC übertragen kann und so habe ich den UDP-Sender einmal mit 100byte großen Pakete, 0ms Verzögerung auf eine nicht vorhanden IP-Adresse in meinem VirtualBox-Subnetz gestartet und mit dem Ressource-Monitor geschaut:

Sicher kann man keine Wunder von einem "Skript" erwarten, aber damit schon 4,4MByte/Sek oder ca. 44MBit zu erzeigen ist zumindest nicht zu verachten. Ein Netzwerk wird man damit aber nicht fluten können.

Ich bin gespannt, ob Messungen auf einer potenteren Plattform mit einem echten LAN andere Werte bringen.

Weitere Links