PowerShell und UDP

Mit PowerShell können Sie auch direkte UDP-Pakete senden und empfangen.

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 überprüfen. Es gibt aber doch den ein oder anderen "ICMP not Reachable". Den senden nämlich Router, wenn der TTL abgelaufen ist. Damit können Sie per UDP auch einen "Traceroute" machen. Windows nutzt dazu ICMP PING aber UNIX-Systeme nutzen dazu auch UDP. Das geht mit UDP auch unter Windows.

$udpClient = new-Object system.Net.Sockets.Udpclient($sourceudpport) 
[string]$buffer = "SendUDP Message by msxfaq"
[int]$remoteudpport=3478
[string]$remoteip = "13.107.8.2"
$byteBuffer = [System.Text.Encoding]::ASCII.GetBytes($Buffer)

1..10 | %{
   $udpclient.ttl = $_;`
    start-sleep -Milliseconds 400; `
   $sentbytes = $udpClient.Send($byteBuffer, $byteBuffer.length, $remoteip, $remoteudpport)`
}

Die Verzögerung mit 400ms habe ich extra gemacht, dass die Antworten in WireShark auch in der richtigen Abfolge sind. Ansonsten würden die 10 UDP-Pakete schnell hintereinander rausgehen.

Damit bleibt nur noch die Frage, wie ich per Software die ICMP Rückmeldung erfasse. Der UDP-Client liefert hier nämlich keine direkte Exception.

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.

Achtung: Vergessen Sie nicht auf der Windows Firewall eine Regel zu addieren, um den Port eingehend zuzulassen.

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 Connect

Interessanterweise gibt es bei de, UDP-Client auch eine "Connect"-Methode, obwohl es mit UDP gar keine Konversation gibt. Die Connect-Methode ist aber ein Weg die entfernte Gegenstelle und den Port der Gegenstellt vorzugeben. Man kann sich dann beim Senden die Angabe dieser Daten ersparen.

Viel wichtiger ist aber, dass dann auch nur Pakete von dieser Quelle angenommen werden. Sie müssen sich in ihrem Code also nicht mit "Fremdpaketen" rumschlagen, die auch auf diesen UDP-Port zugestellt werden. Wobei eine Fälschung der Quell-Adressen mit UDP natürlich sehr einfach ist.

# Versand und Empfang mit beliebigen Zielen mit dem gleichen UDP-Client
$udpClient = new-Object system.Net.Sockets.Udpclient($sourceudpport) 
[void]$udpClient.Send($byteBuffer, $byteBuffer.length, $remoteip, $remoteudpport)

# Versand und Empfang über genau eine vordefinierte Gegenstelle
$udpClient = new-Object system.Net.Sockets.Udpclient($sourceudpport) 
$udpclient.Connect( $remoteip, $remoteudpport)
[void]$udpClient.Send($byteBuffer, $byteBuffer.length)

Allerdings soll es durch den "Connect" möglich sein, dass man ICMP-Meldungen zuordnen kann, d.h. wen ein UDP-Paket nicht ankommt, weil eine Firewall oder Server ein "ICMP not reachable" zurückliefert oder aufgrund von langen Wegen ein "TTL expired" erfolgt. soll man dies ermitteln können. Mir ist das aber leider noch nicht gelungen.

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älschen.

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