514 lines
16 KiB
Go
514 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"runtime"
|
|
"time"
|
|
|
|
probing "github.com/prometheus-community/pro-bing"
|
|
)
|
|
|
|
// Setup Color Palette
|
|
const (
|
|
Black = "\033[1;30m"
|
|
Red = "\033[1;31m"
|
|
Green = "\033[1;32m"
|
|
Yellow = "\033[1;33m"
|
|
Blue = "\033[1;34m"
|
|
Magenta = "\033[1;35m"
|
|
Cyan = "\033[1;36m"
|
|
White = "\033[1;37m"
|
|
Reset = "\033[0m"
|
|
)
|
|
|
|
// Regions
|
|
var regions = []string{
|
|
"Europe",
|
|
"Americas",
|
|
"Asia",
|
|
"Australia",
|
|
}
|
|
|
|
// URLs for each region. Key is the region name, value is a array of objects with the following keys:
|
|
// - url: URL to test
|
|
// - url_friendlyname: Friendly name for the URL. Used in the output.
|
|
|
|
var urls = map[string][]map[string]string{
|
|
"Europe": {
|
|
{
|
|
"url": "uk.ingest.vrcdn.live",
|
|
"url_friendlyname": "UK Ingest (England)",
|
|
},
|
|
{
|
|
"url": "de.ingest.vrcdn.live",
|
|
"url_friendlyname": "DE Ingest (Germany)",
|
|
},
|
|
},
|
|
"Americas": {
|
|
{
|
|
"url": "use.ingest.vrcdn.live",
|
|
"url_friendlyname": "USE Ingest (United States East)",
|
|
},
|
|
{
|
|
"url": "usc.ingest.vrcdn.live",
|
|
"url_friendlyname": "USC Ingest (United States Central)",
|
|
},
|
|
{
|
|
"url": "usw.ingest.vrcdn.live",
|
|
"url_friendlyname": "USW Ingest (United States West)",
|
|
},
|
|
},
|
|
"Asia": {
|
|
{
|
|
"url": "jpe.ingest.vrcdn.live",
|
|
"url_friendlyname": "JPE Ingest (Japan East)",
|
|
},
|
|
{
|
|
"url": "jpw.ingest.vrcdn.live",
|
|
"url_friendlyname": "JPW Ingest (Japan West)",
|
|
},
|
|
},
|
|
"Australia": {
|
|
{
|
|
"url": "au.ingest.vrcdn.live",
|
|
"url_friendlyname": "AUS Ingest (Sydney)",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Define our structs
|
|
type dynamicIngestResult struct {
|
|
packetsTransmitted int
|
|
packetsReceived int
|
|
packetLoss float64
|
|
minPing float64
|
|
maxPing float64
|
|
avgPing float64
|
|
}
|
|
|
|
type regionResult struct {
|
|
region string
|
|
areaName string
|
|
packetsTransmitted int
|
|
packetsReceived int
|
|
packetLoss float64
|
|
minPing float64
|
|
maxPing float64
|
|
avgPing float64
|
|
}
|
|
|
|
func testRegionPing(region string) []regionResult {
|
|
// Get URLs for the region.
|
|
regionUrls := urls[region]
|
|
|
|
// Make the results array for the regions, using the regionRequest struct.
|
|
var regionResults []regionResult = make([]regionResult, len(regionUrls))
|
|
|
|
// Print region name.
|
|
fmt.Printf("%s[ping_test] Testing region %s...%s\n", Yellow, region, Reset)
|
|
|
|
// Test each URL.
|
|
for _, url := range regionUrls {
|
|
fmt.Printf("[ping_test] Testing endpoint %s in region %s with sample rate 10\n", url["url_friendlyname"], region)
|
|
|
|
pinger, err := probing.NewPinger(url["url"])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Listen for Ctrl-C.
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt)
|
|
go func() {
|
|
for range c {
|
|
pinger.Stop()
|
|
fmt.Printf("\n%s[ping_test] Stopped.%s\n", Red, Reset)
|
|
os.Exit(0)
|
|
}
|
|
}()
|
|
|
|
pinger.Count = 10
|
|
pinger.Interval = 100 * time.Millisecond
|
|
pinger.Timeout = 1 * time.Second
|
|
|
|
pinger.OnSend = func(pkt *probing.Packet) {
|
|
// Increment the packetsTransmitted counter.
|
|
regionResults[len(regionResults)-1].packetsTransmitted++
|
|
}
|
|
|
|
pinger.OnRecv = func(pkt *probing.Packet) {
|
|
// Increment the packetsReceived counter.
|
|
regionResults[len(regionResults)-1].packetsReceived++
|
|
}
|
|
|
|
pinger.OnFinish = func(stats *probing.Statistics) {
|
|
// Add our results to the regionResults array.
|
|
regionResults = append(regionResults, regionResult{
|
|
region: region,
|
|
areaName: url["url_friendlyname"],
|
|
packetsTransmitted: regionResults[len(regionResults)-1].packetsTransmitted,
|
|
packetsReceived: regionResults[len(regionResults)-1].packetsReceived,
|
|
packetLoss: stats.PacketLoss,
|
|
minPing: stats.MinRtt.Seconds() * 1000,
|
|
maxPing: stats.MaxRtt.Seconds() * 1000,
|
|
avgPing: stats.AvgRtt.Seconds() * 1000,
|
|
})
|
|
}
|
|
|
|
// If we are in windows, we need to call pinger.SetPrivileged(true) to use ICMP.
|
|
if runtime.GOOS == "windows" {
|
|
pinger.SetPrivileged(true)
|
|
}
|
|
|
|
err = pinger.Run()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
return regionResults
|
|
}
|
|
|
|
func testDynamicIngestPing() dynamicIngestResult {
|
|
// Make a new pinger for ingest.vrcdn.live.
|
|
pinger, err := probing.NewPinger("ingest.vrcdn.live")
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Listen for Ctrl-C.
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt)
|
|
go func() {
|
|
for range c {
|
|
pinger.Stop()
|
|
fmt.Printf("\n%s[ping_test:dynamicIngestTest] Stopped.%s\n", Red, Reset)
|
|
os.Exit(0)
|
|
}
|
|
}()
|
|
|
|
pinger.Count = 10
|
|
pinger.Interval = 100 * time.Millisecond
|
|
pinger.Timeout = 1 * time.Second
|
|
|
|
// Create a empty dynamicIngestResult struct.
|
|
var dynamicIngestResultData dynamicIngestResult
|
|
|
|
pinger.OnSend = func(pkt *probing.Packet) {
|
|
// Increment the packetsTransmitted counter.
|
|
dynamicIngestResultData.packetsTransmitted++
|
|
}
|
|
|
|
pinger.OnRecv = func(pkt *probing.Packet) {
|
|
// Increment the packetsReceived counter.
|
|
dynamicIngestResultData.packetsReceived++
|
|
}
|
|
|
|
pinger.OnFinish = func(stats *probing.Statistics) {
|
|
// Print results.
|
|
fmt.Printf("%s[ping_test:dynamicIngestTest] %d/%d packets received (%.2f%% loss), min/avg/max = %.2f/%.2f/%.2f ms.%s\n", Cyan, stats.PacketsRecv, stats.PacketsSent, stats.PacketLoss, stats.MinRtt.Seconds()*1000, stats.AvgRtt.Seconds()*1000, stats.MaxRtt.Seconds()*1000, Reset)
|
|
|
|
// Add our results to the regionResults array.
|
|
dynamicIngestResultData = dynamicIngestResult{
|
|
packetsTransmitted: dynamicIngestResultData.packetsTransmitted,
|
|
packetsReceived: dynamicIngestResultData.packetsReceived,
|
|
packetLoss: stats.PacketLoss,
|
|
minPing: stats.MinRtt.Seconds() * 1000,
|
|
maxPing: stats.MaxRtt.Seconds() * 1000,
|
|
avgPing: stats.AvgRtt.Seconds() * 1000,
|
|
}
|
|
}
|
|
|
|
// If we are in windows, we need to call pinger.SetPrivileged(true) to use ICMP.
|
|
if runtime.GOOS == "windows" {
|
|
pinger.SetPrivileged(true)
|
|
}
|
|
|
|
err = pinger.Run()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return dynamicIngestResultData
|
|
}
|
|
|
|
func Traceroute(mode string, region string) []string {
|
|
// Figure out the binary we need to use based on the OS.
|
|
var binary string
|
|
if runtime.GOOS == "windows" {
|
|
binary = "tracert"
|
|
} else {
|
|
binary = "traceroute"
|
|
}
|
|
|
|
// Ensure that the binary exists.
|
|
if _, err := exec.LookPath(binary); err != nil {
|
|
fmt.Printf("%s[!] Test Failed: %s %s is not installed. Please install it and try again.\n", Red, Reset, binary)
|
|
os.Exit(1)
|
|
}
|
|
|
|
switch mode {
|
|
case "region":
|
|
// Get URLs for the region.
|
|
regionUrls := urls[region]
|
|
|
|
// Make the results array for the regions, using the regionRequest struct.
|
|
var regionResults []string = make([]string, len(regionUrls))
|
|
|
|
// Print region name.
|
|
fmt.Printf("%s[traceroute_test] Testing region %s...%s\n", Yellow, region, Reset)
|
|
|
|
// Test each URL.
|
|
for _, url := range regionUrls {
|
|
fmt.Printf("[traceroute_test] Testing endpoint %s in region %s\n", url["url_friendlyname"], region)
|
|
|
|
// Run the traceroute command.
|
|
out, err := exec.Command(binary, url["url"]).Output()
|
|
|
|
if err != nil {
|
|
fmt.Printf("%s[!] Test Failed: %s %s failed to run. Please install it and try again.\n", Red, Reset, binary)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Add our results to the regionResults array.
|
|
regionResults = append(regionResults, string(out))
|
|
}
|
|
|
|
return regionResults
|
|
case "dynamicIngest":
|
|
// Run the traceroute command.
|
|
out, err := exec.Command(binary, "ingest.vrcdn.live").Output()
|
|
|
|
if err != nil {
|
|
fmt.Printf("%s[!] Test Failed: %s %s failed to run. Please install it and try again.\n", Red, Reset, binary)
|
|
os.Exit(1)
|
|
}
|
|
|
|
return []string{string(out)}
|
|
}
|
|
|
|
return []string{}
|
|
}
|
|
|
|
func printResults(regionResults []regionResult, dynamicIngestResultData dynamicIngestResult) {
|
|
// Find the best area in our selected region.
|
|
var bestArea regionResult = regionResult{
|
|
avgPing: 9999999999,
|
|
}
|
|
|
|
for _, regionResult := range regionResults {
|
|
if regionResult.areaName == "" {
|
|
continue
|
|
}
|
|
|
|
// Check if the regionResult is better than the best area.
|
|
if regionResult.avgPing < bestArea.avgPing {
|
|
bestArea = regionResult
|
|
} else if regionResult.avgPing == bestArea.avgPing {
|
|
// If the ping is the same, check if the packet loss is better.
|
|
if regionResult.packetLoss < bestArea.packetLoss {
|
|
bestArea = regionResult
|
|
}
|
|
} else {
|
|
// If the ping is worse, skip it.
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Check if dynamic is better than the best area.
|
|
if dynamicIngestResultData.avgPing < bestArea.avgPing {
|
|
bestArea = regionResult{ // Reconstruct the bestArea struct with the dynamicIngestResultData.
|
|
region: "dynamicIngest",
|
|
areaName: "Dynamic Ingest",
|
|
packetsTransmitted: dynamicIngestResultData.packetsTransmitted,
|
|
packetsReceived: dynamicIngestResultData.packetsReceived,
|
|
packetLoss: dynamicIngestResultData.packetLoss,
|
|
minPing: dynamicIngestResultData.minPing,
|
|
maxPing: dynamicIngestResultData.maxPing,
|
|
avgPing: dynamicIngestResultData.avgPing,
|
|
}
|
|
}
|
|
|
|
// Print the results.
|
|
// ping less then 100ms = green
|
|
// ping between 100ms and 200ms = yellow
|
|
// ping more then 200ms = red
|
|
|
|
bestAreaPingColor := Green
|
|
if bestArea.avgPing > 100 && bestArea.avgPing < 200 {
|
|
bestAreaPingColor = Yellow
|
|
} else if bestArea.avgPing > 200 {
|
|
bestAreaPingColor = Red
|
|
}
|
|
|
|
fmt.Printf("Your best area in your region is %s%s%s with a average ping of %.2fms.%s\n", Green, bestArea.areaName, bestAreaPingColor, bestArea.avgPing, Reset)
|
|
|
|
// Print the results for each area in our region.
|
|
for _, regionResult := range regionResults {
|
|
// ping less then 100ms = green
|
|
// ping between 100ms and 200ms = yellow
|
|
// ping more then 200ms = red
|
|
regionResultPingColor := Green
|
|
if regionResult.avgPing > 100 && regionResult.avgPing < 200 {
|
|
regionResultPingColor = Yellow
|
|
} else if regionResult.avgPing > 200 {
|
|
regionResultPingColor = Red
|
|
}
|
|
|
|
// If no area name, continue.
|
|
if regionResult.areaName == "" {
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("%s%s%s: %s%d/%d%s packets received (%s%.2f%%%s loss), min/avg/max = %s%.2f%s/%s%.2f%s/%s%.2f%s ms.%s\n", Cyan, regionResult.areaName, Reset, Cyan, regionResult.packetsReceived, regionResult.packetsTransmitted, Reset, Cyan, regionResult.packetLoss, Reset, Cyan, regionResult.minPing, Reset, regionResultPingColor, regionResult.avgPing, Reset, Cyan, regionResult.maxPing, Reset, Reset)
|
|
}
|
|
|
|
// Print the results for dynamic ingest.
|
|
// ping less then 100ms = green
|
|
// ping between 100ms and 200ms = yellow
|
|
// ping more then 200ms = red
|
|
dynamicIngestResultPingColor := Green
|
|
if dynamicIngestResultData.avgPing > 100 && dynamicIngestResultData.avgPing < 200 {
|
|
dynamicIngestResultPingColor = Yellow
|
|
} else if dynamicIngestResultData.avgPing > 200 {
|
|
dynamicIngestResultPingColor = Red
|
|
}
|
|
|
|
fmt.Printf("%sDynamic Ingest%s: %s%d/%d%s packets received (%s%.2f%%%s loss), min/avg/max = %s%.2f%s/%s%.2f%s/%s%.2f%s ms.%s\n", Cyan, Reset, Cyan, dynamicIngestResultData.packetsReceived, dynamicIngestResultData.packetsTransmitted, Reset, Cyan, dynamicIngestResultData.packetLoss, Reset, Cyan, dynamicIngestResultData.minPing, Reset, dynamicIngestResultPingColor, dynamicIngestResultData.avgPing, Reset, Cyan, dynamicIngestResultData.maxPing, Reset, Reset)
|
|
|
|
// Print the traceroute results.
|
|
fmt.Printf("\nTraceroute results have been saved to traceroute_region.txt and traceroute_dynamicIngest.txt\n")
|
|
|
|
// Print the disclaimer.
|
|
fmt.Printf("\nDisclaimer: This tool should not be used as a 100%% accurate way to determine your best region or a possible network issue. Please work with official VRCDN support to determine the cause of any issues you may be having.\n\n")
|
|
|
|
// Print warning about traceroute.
|
|
fmt.Printf("%s%s%s\n", Yellow, "WARNING: Traceroute results contain SENSITIVE data. Please share with care!", Reset)
|
|
}
|
|
|
|
func main() {
|
|
fmt.Printf("VRCDN Network Test v1.0, built with %s\n", runtime.Version())
|
|
fmt.Print("Created by sticksdev. This tool is not affiliated with VRCDN and is community-made. See LICENSE for more information.\n\n")
|
|
|
|
fmt.Print("This tool will test your connection to the VRCDN network. Please select your closest region.\n")
|
|
|
|
// Print regions.
|
|
for i, region := range regions {
|
|
fmt.Printf("%d. %s\n", i+1, region)
|
|
}
|
|
|
|
// Get user input.
|
|
var region int
|
|
var errorInput error
|
|
|
|
// Keep asking for input until a valid region is selected.
|
|
for {
|
|
fmt.Print("Please enter the number of your region: ")
|
|
fmt.Scan(®ion, &errorInput)
|
|
|
|
// If the input is valid, break out of the loop.
|
|
if region > 0 && region <= len(regions) {
|
|
break
|
|
} else {
|
|
fmt.Print("\nInvalid region selected. Please try again.")
|
|
|
|
// Sleep for .5 seconds
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
if errorInput != nil {
|
|
fmt.Print(Red + "[!]" + Reset + " a error occurred while reading your input. Please try again.\n")
|
|
fmt.Print(errorInput.Error() + "\n")
|
|
|
|
// Sleep for .5 seconds
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Test the region.
|
|
var regionResults []regionResult = testRegionPing(regions[region-1])
|
|
|
|
if len(regionResults) == 0 {
|
|
fmt.Printf("%s[!]%s No results were returned from the previous test. Please try again.\n", Red, Reset)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("[ping_test] Running dynamic ingest test... (ingest.vrcdn.live)\n")
|
|
var dynamicIngestResultData dynamicIngestResult = testDynamicIngestPing()
|
|
|
|
if dynamicIngestResultData == (dynamicIngestResult{}) {
|
|
fmt.Printf("%s[!]%s No results were returned from the previous test. Please try again.\n", Red, Reset)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var traceRegionResults []string = Traceroute("region", regions[region-1])
|
|
|
|
if len(traceRegionResults) == 0 {
|
|
fmt.Printf("%s[!]%s No results were returned from the previous test. Please try again.\n", Red, Reset)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("[traceroute_test] Running dynamic ingest test... (ingest.vrcdn.live)\n")
|
|
var traceDynamicIngestResults []string = Traceroute("dynamicIngest", "")
|
|
|
|
if len(traceDynamicIngestResults) == 0 {
|
|
fmt.Printf("%s[!]%s No results were returned from the previous test. Please try again.\n", Red, Reset)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("%s[...]%s Saving traceroute results to traceroute_region.txt and traceroute_dynamicIngest.txt\n", Yellow, Reset)
|
|
|
|
// Save the traceroute results to a file.
|
|
var traceRegionFile, traceDynamicIngestFile *os.File
|
|
|
|
// If we have old results, delete them.
|
|
if _, err := os.Stat("traceroute_region.txt"); err == nil {
|
|
os.Remove("traceroute_region.txt")
|
|
}
|
|
|
|
if _, err := os.Stat("traceroute_dynamicIngest.txt"); err == nil {
|
|
os.Remove("traceroute_dynamicIngest.txt")
|
|
}
|
|
|
|
// Define error.
|
|
var err error
|
|
|
|
traceRegionFile, err = os.OpenFile("traceroute_region.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
fmt.Printf("%s[!]%s Failed to open traceroute_region.txt. Please try again.\n", Red, Reset)
|
|
os.Exit(1)
|
|
}
|
|
|
|
traceDynamicIngestFile, err = os.OpenFile("traceroute_dynamicIngest.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
fmt.Printf("%s[!]%s Failed to open traceroute_dynamicIngest.txt. Please try again.\n", Red, Reset)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Write the results to the files.
|
|
for _, traceRegionResult := range traceRegionResults {
|
|
traceRegionFile.WriteString(traceRegionResult)
|
|
}
|
|
|
|
for _, traceDynamicIngestResult := range traceDynamicIngestResults {
|
|
traceDynamicIngestFile.WriteString(traceDynamicIngestResult)
|
|
}
|
|
|
|
// Close the files.
|
|
traceRegionFile.Close()
|
|
traceDynamicIngestFile.Close()
|
|
|
|
fmt.Printf("%s[i]%s Save complete. Showing results!\n", Green, Reset)
|
|
printResults(regionResults, dynamicIngestResultData)
|
|
|
|
// Sleep for 5 seconds.
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Exit.
|
|
os.Exit(0)
|
|
}
|