# End2End-udp3478 # Simple Skype for Business Online Edge Turn test # # 20180414 Added PRTG output # 20180416 added RTT min/max/avg # 20180425 fixes for PowerShell2 and PRTG detection and using 50019 as source port. 50000 is used to often # 20180504 Erweiterung ArchivCSV # 20180507 Umstellen auf PRTG Push und Dauerbetrieb # 20181123 Fix bei Aufruf von sendto-prtg # 20181205 Erweiterung um Analyse der Rückantwort # 20181211 erweiterung um Teams IP # 20190122 Pretty Output Colorcoding # 20190318 Missing paket during measuerment in magenta, fix Getkey mit Sleeptime 0 # 20190705 Parameterreihenfolge optimiert, DNS-Auflösung addiert # 20190711 Erweiterung um CSV-Datei Timestamp und TraceRoute deaktivert, Regions addiert, mode addiert, Average auf intervall statt Count # Ausgabe optimiert, Default Werte angepasst # 20190712 answerreceived als Int statt Bool,, Rename Averagettl-> avgroundtrip, Rename averageintervalsec -> avgintervalsec Status addiert (bitmask) # 20190903 Bugfix summarystatur +=64 # 20191007 update to handle network link loss (WLAN) # 20191008 title korrigiert. some more color enhancements # 20191009 Pause addiert und [System.Console]::ReadKey($true) addiert # 20200214 nicer legend in color, capture invalid negative durations and to high # 20200214 Usage of $stopwatch =New-Object system.diagnostics.stopwatch instead of Get-Date # 20200612 Change Default remote point to Teams # 20200831 PRTG-Integration korrigiert # 20210127 Default RemoteIPs auf DNS-Namen umgestellt # 20210204 Tippfehler, validateSet angepasst, SkypeOnline Endpunkt addiert, WindowTitle # 20210205 Fix DNS to use IPv4 adddress only # 20210206 CSV-Dateiname erweitert um Zielhost # # Pending # Iv6 Support # [cmdletbinding()] param( [ValidateSet("TTLCheck", "end2end")] [string]$mode = "end2end", # use templates and overwrite some default RTT, END2END [Validatescript({$_ -in ("o365","teams","skype") -or ($_ -as [ipaddress]) -or ($_ -as [string])})] [string]$remotehost = "teams", # use static IP-Address or "O365" or "Teams" or "" [ValidateRange(0,65535)] [int]$sourceudpport=50019, # Source port to send UDP from. SfB normally uses 50000-50019 [ValidateRange(0,65535)] [int]$remoteudpport=3478, # Default STUN/TURN Port [ValidateRange(0,128)] [byte]$maxttl, # start with TTL32 for Countdown [int]$maxretries, # repeatcount if packet was not received [int]$avgintervalsec, # measure response with max TTL and calcualte average and max [long]$interpacketsleepms, # delay between every packet [long]$sleeptime, # time in seconds to sleep between two checks [long]$ReceiveTimeout = 500, # So lange wartet der UDP-Stack [switch]$sendtopipeline=$false, # [string]$resultcsv = ".\End2End-udp3478_$($env:COMPUTERNAME)-$($remotehost).csv", # store results [string]$prtgpushurl="" # specify PRTG-Push Url if required like http://prtg:5050/sensorid ) function checkexitorpause { [console]::TreatControlCAsInput = $true if ([console]::KeyAvailable) { $keycode = [System.Console]::ReadKey($true) if ($keycode.key -eq "X") { write-host "X" -ForegroundColor Magenta -nonewline return $true } elseif (($keycode.modifiers -band [consolemodifiers]"control") -and ($keycode.key -eq "C")) { write-host "CTRL-c" -ForegroundColor Magenta -nonewline return $true } elseif ($keycode.key -eq "P") { write-host "P" -ForegroundColor Magenta -nonewline $keycode = [System.Console]::ReadKey($true) if ($keycode.key -eq "X") { write-host "X" -ForegroundColor Magenta -nonewline return $true } else { return $false } } else { return $false } } else { return $false } } write-host "End2End-UDP3478:Start" $host.ui.RawUI.WindowTitle = "End2End-UDP3478" #region Set Parameters for mode templates if ($mode -eq "TTLCheck") { Write-host " Mode : RTT. Short RTT Check and distance measurement" if (!$maxttl) {$maxttl=20} if (!$maxretries) {$maxretries=5} if (!$avgintervalsec) {$avgintervalsec=1} if (!$interpacketsleepms){$interpacketsleepms = 0} if (!$sleeptime) {$sleeptime =0 } } elseif ($mode -eq "END2END") { Write-host " Mode : END2END. continuous latency check and no distance check" if (!$maxttl) {$maxttl=128} if (!$maxretries) {$maxretries=0} if (!$avgintervalsec) {$avgintervalsec=60} if (!$interpacketsleepms){$interpacketsleepms =20} if (!$sleeptime) {$sleeptime=0} } else { Write-host "Mode not found -Exiting" exit 1 } Write-host " MaxTTL : $($maxttl)" Write-host " MaxRetries : $($maxretries)" Write-host " AvgIntervalSec : $($avgintervalsec)" Write-host " InterpacketsleepMS : $($interpacketsleepms)" Write-host " Sleeptime : $($sleeptime)" Write-host " prtgpushurl : $($prtgpushurl)" $host.ui.RawUI.WindowTitle = "End2End-UDP3478 to $($remotehost)" #endregion #region Determine Edge-Server Write-host " TURN-Server : " -NoNewline if ($remotehost -eq "o365") { write-host "Use Skype for Business EMEA Online Servers: " -NoNewline [string]$remotehost = "mediaedge1E.online.lync.com" # Anycast IP of Office 365 TURN Servers Europe } elseif ($remotehost -eq "teams") { write-host "Use Office 365 Microsoft Teams Server: " -NoNewline [string]$remotehost = "worldaz.tr.teams.microsoft.com" # Anycast IP of a teams Turn Server } elseif ($remotehost -eq "skype") { write-host "Use Skype Consumer Server: " -NoNewline [string]$remotehost = "global.tr.skype.com" # Anycast IP of a teams Turn Server } elseif ($remotehost -as [ipaddress]) { write-host "Use given IP-Address: $($remotehost)" -NoNewline } else { try { write-host "Use given DNS-Name: $($remotehost) " -NoNewline $remotehost = ([System.Net.Dns]::GetHostAddresses($remotehost) | Where-Object {$_.AddressFamily -eq "InterNetwork"})[0].IPAddressToString } catch { Write-host "Unable to resolve -Exit" -backgroundColor red exit } } Write-host "IP=$($remotehost)" #endregion #Region Prepare UDP Environment Write-host "End2End-UDP3478:Start UDP-Client on $($sourceudpport)" $udpClient = new-Object System.Net.Sockets.Udpclient($sourceudpport) $udpClient.Client.ReceiveTimeout = $ReceiveTimeout #$udpClient.Client.Blocking = $true $udpclient.ttl = $maxttl # 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) $remotehostEndPoint = New-Object System.Net.IPEndPoint([system.net.IPAddress]::Parse("0.0.0.0"),0); try{ Write-host "End2End-UDP3478:Connect UDPClient to $($remotehost):$($remoteudpport)" #$udpclient.Connect($remotehost, $remoteudpport) } catch { Write-host "Unable to Connect UDP-Socket SourcePort: $($sourceudpport) Remote: $($remotehost):$($remoteudpport)" exit 1 } #endregion $ASCIIEncoder = new-object System.Text.ASCIIEncoding [string]$processindicator=".|/-\" Write-host "Colorcode:" -nonewline Write-Host "<=100ms" -nonewline -backgroundcolor green -foregroundcolor black Write-Host "<=200ms" -nonewline -backgroundcolor yellow -foregroundcolor black Write-Host ">200ms" -backgroundcolor red write-host "Legend: 100 pakets max: . = max<100ms W= max<200ms E=max>200ms" write-host "End2End-UDP3478:Keyboard: use X=End P=Pause" -BackgroundColor Blue $stopscript=$false while (!$stopscript) { write-verbose " Init summary Table" $summary = [pscustomobject][ordered] @{ timestamp = ((get-date).ToUniversalTime().tostring("u")) mode=$mode remoteip=$remotehost remoteudpport =$remoteudpport minroundtrip=[int]9999 avgroundtrip=[int]0 maxroundtrip=[int]0 successrttcount=[int]0 failedrttcount=[int]0 status=[int]0 answerreceived=[int]0 } # temp storage to 100 paket statistics $avg100packet=0 $max100packet=0 write-verbose " Calculate average Roundtrip:" $averagestart = get-date Write-host "$($summary.timestamp):RTT:" -NoNewline [long]$loopcount=0 [bool]$packetsend=$false while ( ((get-date)-$averagestart).totalseconds -lt $avgintervalsec) { if (checkexitorpause) { # leave loop $stopscript=$true break } $loopcount++ # write CountDown Write-host " $(($avgintervalsec- ((get-date)-$averagestart).totalseconds).tostring("0000")) Seconds`b`b`b`b`b`b`b`b`b`b`b`b`b" -NoNewline $stopwatch =New-Object system.diagnostics.stopwatch $stopwatch.start() try { Write-Verbose "Send UDP-Paket from $($udpclient.client.LocalEndPoint) to $($udpclient.client.RemoteEndPoint)" #write-host "S" -ForegroundColor yellow -nonewline $starttime = get-date #$null = $udpClient.Send($byteBuffer, $byteBuffer.length) $null = $udpClient.Send($byteBuffer, $byteBuffer.length,$remotehost,$remoteudpport) [bool]$packetsend=$true } catch { write-host "S`b" -ForegroundColor Red -NoNewline #$_.exception $summary.failedrttcount++ write-verbose "Error Sending packet. Check Network" [bool]$packetsend=$false } if ($packetsend) { try { # wait for incoming packet $receiveddata = $udpClient.Receive([ref]$remotehostendpoint) $roundtriptimems = [int](((get-date) - $starttime).totalmilliseconds) $roundtriptimems=$stopwatch.ElapsedMilliseconds if ($receiveddata){ write-Verbose "Answer received" if ($ASCIIEncoder.GetString($receiveddata).Contains("The request did not contain a Message-Integrity attribute")) { write-host "$($processindicator[$loopcount%$processindicator.Length])`b" -ForegroundColor Green -nonewline $summary.successrttcount++ write-verbose " $($roundtriptimems ) ms" $summary.avgroundtrip = $summary.avgroundtrip + $roundtriptimems if ($summary.maxroundtrip -lt $roundtriptimems) { $summary.maxroundtrip = $roundtriptimems } if ($summary.minroundtrip -gt $roundtriptimems) { $summary.minroundtrip = $roundtriptimems } if ($avg100packet -eq 0) { $avg100packet = $roundtriptimems } else { $avg100packet+= $roundtriptimems } if ($max100packet -lt $roundtriptimems) { $max100packet = $roundtriptimems } } else { write-host "$($processindicator[$loopcount%$processindicator.Length]) " -ForegroundColor red-nonewline $summary.failedrttcount++ } } else { Write-host "?" -foregroundcolor yellow -NoNewline $summary.failedrttcount++ } } catch { #$_ #write-host "NO Answer Received 2" -foregroundcolor yellow write-host "E" -ForegroundColor Red -NoNewline #$_.exception $summary.failedrttcount++ } } if ($loopcount%100 -eq 0) { # intermdiary status after 100 pakets based on latency >100m U+2584 if ($max100packet -lt 100) { $statuscharacter= "." } elseif ($max100packet -lt 200) { $statuscharacter= "W" } else { $statuscharacter= "E" } # avg is sum of 100 packets if ($avg100packet-lt 10000) { write-host "$($statuscharacter)" -backgroundColor green -NoNewline -ForegroundColor black } elseif ($avg100packet -lt 20000) { write-host "$($statuscharacter)" -backgroundColor Yellow -NoNewline -ForegroundColor black } else { write-host "$($statuscharacter)" -backgroundColor red -NoNewline -ForegroundColor white } $max100packet=0 $avg100packet=0 } Start-Sleep -milliseconds $interpacketsleepms } if ($summary.successrttcount -eq 0) { $summary.avgroundtrip = 0 } else { $summary.avgroundtrip = [int]($summary.avgroundtrip / $summary.successrttcount) $summary.answerreceived=1 # Write minimumRTT colored write-host "(Min/Avg/Max):" -NoNewline if ($summary.minroundtrip -lt 100) { write-host "$($summary.minroundtrip.tostring("000"))" -ForegroundColor green -NoNewline } elseif ($summary.minroundtrip -lt 200) { write-host "$($summary.minroundtrip.tostring("000"))" -ForegroundColor Yellow -NoNewline $summary.status+=1 } else { write-host "$($summary.minroundtrip.tostring("000"))" -ForegroundColor red -NoNewline $summary.status+=8 } Write-host "/" -NoNewline # Write avgroundtrip colored if ($summary.avgroundtrip -lt 100) { write-host "$($summary.avgroundtrip.tostring("000"))" -ForegroundColor green -NoNewline } elseif ($summary.avgroundtrip -lt 200) { write-host "$($summary.avgroundtrip.tostring("000"))" -ForegroundColor Yellow -NoNewline $summary.status+=2 } else { write-host "$($summary.avgroundtrip.tostring("000"))" -ForegroundColor red -NoNewline $summary.status+=16 } Write-host "/" -NoNewline # Write maxRTT colored if ($summary.maxroundtrip -lt 100) { write-host "$($summary.maxroundtrip.tostring("000"))" -ForegroundColor green -NoNewline } elseif ($summary.maxroundtrip -lt 200) { write-host "$($summary.maxroundtrip.tostring("000"))" -ForegroundColor Yellow -NoNewline $summary.status+=4 } else { write-host "$($summary.maxroundtrip.tostring("000"))" -ForegroundColor red -NoNewline $summary.status+=32 } # Write Total packet Count write-host " Total/Fail:$($summary.successrttcount.tostring("000"))/"-nonewline if ($summary.failedrttcount -eq 0) { write-host "$($summary.failedrttcount.tostring("000"))" -ForegroundColor green -nonewline } else { write-host "$($summary.failedrttcount.tostring("000"))" -ForegroundColor red -nonewline $summary.status+=64 } } #Region Measure Distance with descending TTL if ($mode -eq "TTLCheck") { write-verbose " Start TTL Distance Check" [int]$retrycount=1 $result=@{} Write-host " TTLCheck: " -nonewline for ($ttl=$maxttl; $ttl -gt 0 ; $ttl--) { if (checkexitorpause) { # leave while loop $stopscript=$true break } Write-Verbose " Send STUN-Request TTL $($ttl) " $udpclient.ttl = $ttl $null = $udpClient.Send($byteBuffer, $byteBuffer.length,$remotehost,$remoteudpport) try { $result[$ttl]=$udpClient.Receive([ref]$remotehostendpoint) write-host "`b`b`b.$($ttl.ToString("000"))" -BackgroundColor green -ForegroundColor black -NoNewline $summary.answerreceived=1 } catch { #$_ write-host "`b`b`b.$($ttl.ToString("000"))" -BackgroundColor red -ForegroundColor black -NoNewline write-verbose " NO Answer received. Try $($retrycount)" if ($retrycount -ge $maxretries){ break } $retrycount++ $ttl++ } } if ($summary.answerreceived -eq 1) { write-host " MinTTL:$($ttl+1)" -NoNewline } else { write-host " NoConnection" -NoNewline } } #endregion write-host "" #Region Write result to CSV-File if ($resultcsv -ne ""){ write-verbose " Export Data to ArchivCSV $($resultcsv)" $summary | export-csv -path $resultcsv -append -notypeinformation } #endregion #region Write summary output to pipeline if ($sendtopipeline){ $summary } #endregion #region Send data to PRTG if enabled if ($env:cPRTGPUSHURL) { write-host "SendTo-PRTG: Post Result to PRTGProbe $($env:prtgpushurl)" $Scriptname = (split-path $MyInvocation.ScriptName -Leaf).replace(".ps1","") $prtgpushurl = "$($env:PRTGPUSHURL)/$($scriptname)_$($env:COMPUTERNAME)" } if ($prtgpushurl -ne "") { write-verbose "end2end-udp3478: Build PRTG XML" $prtgresult = "" if ($summary.answerreceived -eq 1) { $prtgresult+= "" $prtgresult+= " " $prtgresult+= " Hopcount" $prtgresult+= " $($ttl+1)" $prtgresult+= " Custom" $prtgresult+= " Hops" $prtgresult+= " 0" $prtgresult+= " " $prtgresult+= " " $prtgresult+= " RTT Avg" $prtgresult+= " $($summary.avgroundtrip)" $prtgresult+= " Custom" $prtgresult+= " ms" $prtgresult+= " 0" $prtgresult+= " " $prtgresult+= " " $prtgresult+= " RTT Max" $prtgresult+= " $($summary.maxroundtrip)" $prtgresult+= " Custom" $prtgresult+= " ms" $prtgresult+= " 0" $prtgresult+= " " $prtgresult+= " " $prtgresult+= " RTT Min" $prtgresult+= " $($summary.minroundtrip)" $prtgresult+= " Custom" $prtgresult+= " ms" $prtgresult+= " 0" $prtgresult+= " " $prtgresult+= " 0" $prtgresult+= " Reply got from $($remotehost):$($remoteudpport) in $($ttl) hops" $prtgresult+= "" } else { $prtgresult+= "" $prtgresult+= " 1" $prtgresult+= " Unabled to Connect to $($remotehost):$($remoteudpport) in $($maxttl) hops" $prtgresult+= "" } Write-Verbose "PRTG: $($prtgpushurl)" try { $Answer=Invoke-RestMethod ` -method "GET" ` -timeout 5 ` -URI ("$($prtgpushurl)?content=$($prtgresult)") if ($answer."Matching Sensors" -eq "1") { write-Verbose "PRTG" } elseif ($answer."Matching Sensors" -eq "0") { write-host "PRTG" -ForegroundColor Yellow } else { write-host "PRTG" -ForegroundColor Magenta $answer } } catch { Write-host "PRTG" -ForegroundColor red } } #endregion Send data to PRTG if enabled write-Verbose "Sleeping $($sleeptime) Seconds. Press ""X""-key to stop after next try" for($count = $sleeptime; $count -ge 0; $count--) { write-progress -activity "Sleeping X=eXit script, C=Continue" -SecondsRemaining $count if (checkexitorpause) { $stopscript =$true } if ($count -gt 0) { start-sleep -seconds 1 } } write-progress -activity "Sleeping X=eXit script, C=Continue" -Completed } write-host "End2End-UDP3478:Close UDP-Socket" $udpClient.close() write-host "End2End-UDP3478:End"