Traceroute TURN

Ein einfacher Traceroute prüft per ICMP oder UDP die Erreichbarkeit einer Gegenstelle und versucht den Weg aufzuzeichnen. Gerade beim Einsatz von VoIP mit Skype for Business und Teams ist es wichtig den Weg der Pakete vom Client zum STUN/TURN-Server zu kennen. In einem guten Setup ist es möglich eine UDP-Verbindung zum Port 3478 der Edge-Server zu starten. Realtime-Traffic (Sprache und Video) sollte aufgrund der speziellen Anforderungen (Realtime. viele kleine Pakete, kein Resend erforderlich) nie in TCP, HTTPS-Tunnel oder VPN eingekapselt werden.

Traceroute mit UDP Port 3478

Auf der Seite End2End-UDP3478 habe ich mir ein Skript gebaut, welches einen ICE-Handshake mit einen Skype for Business Edge-Server versucht. Es ist einfacher Verbindungtest. Diesen Code habe ich etwas verfeinert, um mehrere UDP-Anfragen mit aufsteigendem TTL zu senden:

# Simple Skype for Business Online Edge Turn test

param (
   [int]$sourceudpport=50000,
   [int]$remoteudpport=3478,
   [string]$remoteip = "13.107.8.2",
   [byte]$maxttl=20
)

Write-host "Start UDP-Client on $($sourceudpport)"
$udpClient = new-Object System.Net.Sockets.Udpclient($sourceudpport) 
$udpClient.Client.ReceiveTimeout = 1000   

# STUN Packet from SfB Network Assessment Tool
$byteBuffer = @(0x00,0x03,0x00,0x54,0x21,0x12,0xa4,0x42,0xd2,0x79,0xaa,0x56,0x87,0x86,0x48,
                0x73,0x8f,0x92,0xef,0x58,0x00,0x0f,0x00,0x04,0x72,0xc6,0x4b,0xc6,0x80,0x08,
                0x00,0x04,0x00,0x00,0x00,0x04,0x00,0x06,0x00,0x30,0x04,0x00,0x00,0x1c,0x00,
                0x09,0xbe,0x58,0x24,0xe4,0xc5,0x1c,0x33,0x4c,0xd2,0x3f,0x50,0xf1,0x5d,0xce,
                0x81,0xff,0xa9,0xbe,0x00,0x00,0x00,0x01,0xeb,0x15,0x53,0xbd,0x75,0xe2,0xca,
                0x14,0x1e,0x36,0x31,0xbb,0xe3,0xf5,0x4a,0xa1,0x32,0x45,0xcb,0xf9,0x00,0x10,
                0x00,0x04,0x00,0x00,0x01,0x5e,0x80,0x06,0x00,0x04,0x00,0x00,0x00,0x01)

$RemoteIpEndPoint = New-Object System.Net.IPEndPoint([system.net.IPAddress]::Parse("0.0.0.0"),0);
$result=@{}
Write-host "Connect to $($remoteip):$($remoteudpport)"
$udpclient.Connect($remoteip, $remoteudpport)

for ($ttl=1; $ttl -le $maxttl; $ttl++) {
   Write-host "Send STUN-Request with TTL $($ttl) " -nonewline
   $udpclient.ttl = $ttl
   $sentbytes = $udpClient.Send($byteBuffer, $byteBuffer.length)
   try {
   $result[$ttl]=$udpClient.Receive([ref]$remoteIpendpoint)
     write-host "Answer received"
      break
   }
   catch {
      write-host "NO Answer received"
   }
}
Write-host "Closing UDP"
$udpClient.close()
$udpClient.dispose()
Write-host "Done"

Das Skript können Sie einfach ausführen und wenn ihr Client mit dem Quellport 50.000 den Skype for Business Online Server "13.107.8.2" auf Port 3478/UDP erreichen kann, dann wird das Skript irgendwann mit einer Erfolgsmeldung antworten.

PS C:\temp> .\end2end-stun.ps1 
Start UDP-Client on 50000
Connect to 13.107.8.2:3478
Send STUN-Request with TTL 1 NO Answer received
Send STUN-Request with TTL 2 NO Answer received
Send STUN-Request with TTL 3 NO Answer received
Send STUN-Request with TTL 4 NO Answer received
Send STUN-Request with TTL 5 NO Answer received
Send STUN-Request with TTL 6 NO Answer received
Send STUN-Request with TTL 7 NO Answer received
Send STUN-Request with TTL 8 NO Answer received
Send STUN-Request with TTL 9 NO Answer received
Send STUN-Request with TTL 10 NO Answer received
Send STUN-Request with TTL 11 NO Answer received
Send STUN-Request with TTL 12 NO Answer received
Send STUN-Request with TTL 13 NO Answer received
Send STUN-Request with TTL 14 Answer received
Closing UDP
Done

Wenn Sie mit Wireshark (vormals Ethereal) die Pakete mitschneiden, dann ist gut der aufsteigende TTL zu sehen

Die ersten beiden Stationen senden noch ein "TTL exceeded" zurück. Interessant, dass Sie dies mit einem Start TTL von 64 machen. Die nächsten Pakete 3,4 und 5 laufen quasi ins "Leere", zumindest kommt kein "TTL exceeded" zurück. Paket 6 wird wieder aktiv gemeldet und der Absender scheint einen TTL von 254 zu nutzen, denn bei mir kommt ein TTL von 248 an, was 6 Hops entspricht. Paket 7 und 8 ist wieder unbekannt ehe dann 9,10,11 wieder gemeldet werden. Nachdem dann 12,13,14 ohne Bericht verworfen werden kommt das Paket mit dem TTL 15 bei der Gegenstelle an und wird auch beantwortet. Es kommt mit einem TTL 115 zurück und auch alle nachfolgenden Pakete kommen mit dem gleichen TTL an. Wie viele Hops auf dem Rückweg genommen werden, kann man aber nicht ermitteln da der Startwert von der Gegenstelle vorgegeben wird. Ich kann nur vermuten, dass es vielleicht 128 ist und dann 13 Hops anstehen. Allerdings wäre das dann weniger Stationen als auf dem Hinweg mit 14. Ich bekomme aber schon einen einfachen Trace damit hin:

Herausforderung ICMP-Verarbeitung

Die Herausforderung bei diesem Skript ist es, die ICMP-Rückmeldungen zuzuordnen. Nun ist ICMP aber ein komplett anderes Protokoll und kann nicht über den Powershell UDP-Client empfangen werden. Ich hätte ja gerne eine Funktion gesehen, dass der UDPClient.Receive mit einer Exception abbricht aber dennoch etwas zurück liefert. Nämlich das ICMP-Paket, welches bei einigen Routern durchaus umfangreiche Daten beinhalten kann. Hier ein solches Paket in Wireshark

Leider scheint das NET-Framework solche ICMP-Pakete nicht dem Client wieder zuzuordnen oder anderweitig für das Programm verfügbar zu machen. Das steht so noch auf meiner Wunschliste. Hier erst ein mal ein paar Links, die ich bei der Recherche dazu gefunden aber mir noch keine Lösung gebracht haben. Vermutlich müsste ich selbst ICMP annehmen und zuordnen oder mich auf WINSOCK-Ebene herunter begeben. Ein Versuch meinerseits das per Skript zu machen, hat zumindest noch keinen Erfolg verbucht. Wenn ich aber die folgenden Links anschaue, dann sollte es machbar sein:

Dann bleibt nur die Auswertung der ICMP-Antwort, dass Sie auch zu meiner UDP-Verbindung gehört. Mit dem folgenden Code-Schnipsel ist es mir zumindest schon gelungen die ICMP-Pakete zu bekommen. Allerdings muss das Skript dazu als Administrator gestartet werden. Ich finde es aber schon interessant, dass man so quasi auch fremde Pakete mitschneiden kann.

#ICMP-Receiver
$RemoteIpEndPoint = New-Object System.Net.IPEndPoint([system.net.IPAddress]::Parse("0.0.0.0"),0);
$socket = New-Object System.Net.Sockets.Socket(
                                                [System.Net.Sockets.AddressFamily]::InterNetwork,
                                                [System.Net.Sockets.SocketType]::Raw,
                                                [System.Net.Sockets.ProtocolType]::Icmp)
$socket.Bind($RemoteIpEndPoint);
$socket.iocontrol( `
   [Net.Sockets.IOControlCode]::ReceiveAll, 
   [BitConverter]::GetBytes(1),
   $null)
$socket.ReceiveTimeout = 5000

$ReceiveBuffer = New-Object Byte[] 256
$inBytes = $socket.ReceiveFrom($ReceiveBuffer, $ReceiveBuffer.length, 0, [ref]$RemoteIpEndPoint);
if ($ReceiveBuffer[20] -eq 11) {   # ICMP type = Delivery failed
   Write-host "TTL Exceeded"
   Write-host "  Sender: $($RemoteIpEndPoint.ToString())"
   Write-host "Destination: $($ReceiveBuffer[44]).$($ReceiveBuffer[45]).$($ReceiveBuffer[46]).$($ReceiveBuffer[47])"
}
else {
   Write-host "ICMP-Paket $($ReceiveBuffer[20]) received"
}

Aktuell habe ich aber noch nicht den Code soweit, dass ich die Rückantworten der Zwischenstationen erfasse und auswerte. Ich beschränke mich daher auf die "Entfernungsmessung" und generelle Erreichbarkeit

Aufgrund von Missbrauchs durch Viren um das Jahr 2000 hat Microsoft die Verwendung von Raw-Sockets auf Nicht-Server-Editionen des Windows-Betriebssystems eingeschränkt.

On Windows 7, Windows Vista, Windows XP with Service Pack 2 (SP2), and Windows XP with Service Pack 3 (SP3), the ability to send traffic over raw sockets has been restricted in several ways:
https://docs.microsoft.com/en-us/windows/win32/winsock/tcp-ip-raw-sockets-2#limitations-on-raw-sockets

Hier noch ein paar weiterführende Links:

Weitere Links