Refactor
This commit is contained in:
parent
adcb3fc5f4
commit
170c8b31b1
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
/.push-*
|
||||
/.container-*
|
||||
/.dockerfile-*
|
||||
.idea
|
||||
|
@ -1,188 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
ct "github.com/daviddengcn/go-colortext"
|
||||
ui "github.com/manifoldco/promptui"
|
||||
"github.com/nuttapp/pinghist/ping"
|
||||
)
|
||||
|
||||
// Set the banner for advertisement
|
||||
const BANNER = "ICAgKiAgICAgICAgICAgICAgICAgICgKICggIGAgICAgICAgICAgKCAgICggIClcICkgICAgICAgICAgICAgICkKIClcKSkoICAgICAoICAgKVwgIClcKCgpLyggICAgKSAgICAgICggLygKKChfKSgpXCAgICkpXCAoKF8pKChfKS8oXykpKCAvKCAgKCAgIClcKCkpCihfKCkoKF8pIC8oKF8pIF8gICBfIChfKSlffCkoXykpIClcIChfKSkvCnwgIFwvICB8KF8pKSggfCB8IHwgfHwgfF8gKChfKV8gKChfKXwgfF8KfCB8XC98IHx8IHx8IHx8IHwgfCB8fCBfX3wvIF9gIHwoXy08fCAgX3wKfF98ICB8X3wgXF8sX3x8X3wgfF98fF98ICBcX18sX3wvX18vIFxfX3wK"
|
||||
func printResults(active []*Server) {
|
||||
// we already printed banner i think
|
||||
// ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
// fmt.Printf("\n\n%s\n", banner())
|
||||
|
||||
// Set the Mullvad API URL
|
||||
const URL = "https://api.mullvad.net/www/relays/all"
|
||||
cPrintln(ct.Magenta, `
|
||||
==========================================
|
||||
============ TOP 10 RESULTS ==============
|
||||
==========================================
|
||||
`)
|
||||
|
||||
// Server struct
|
||||
type Server struct {
|
||||
List []float64
|
||||
Last float64
|
||||
URL string
|
||||
Hostname string
|
||||
Country_code string
|
||||
Country_name string
|
||||
City_name string
|
||||
Active bool
|
||||
Type string
|
||||
}
|
||||
|
||||
// Variable server lists
|
||||
var (
|
||||
servers []*Server
|
||||
active []*Server
|
||||
)
|
||||
|
||||
// Type for server lists
|
||||
type ByLast []*Server
|
||||
|
||||
// Length method
|
||||
func (a ByLast) Len() int { return len(a) }
|
||||
|
||||
// Swap method
|
||||
func (a ByLast) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// Less method
|
||||
func (a ByLast) Less(i, j int) bool { return a[i].Last < a[j].Last }
|
||||
|
||||
// Ping server to determine latency
|
||||
func (s *Server) Ping() (float64, error) {
|
||||
// for i := 0; i < 3; i++ {
|
||||
ct.ChangeColor(ct.Cyan, false, ct.None, false)
|
||||
fmt.Println("[*] Testing server:", s.URL)
|
||||
|
||||
r, err := ping.Ping(s.URL)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if r.Time < 1 {
|
||||
r.Time = 999999999999
|
||||
}
|
||||
|
||||
s.List = append(s.List, r.Time)
|
||||
s.Last = r.Time
|
||||
return s.Last, nil
|
||||
}
|
||||
|
||||
// main function
|
||||
func main() {
|
||||
// Decode banner
|
||||
banner, err := base64.RawStdEncoding.DecodeString(BANNER)
|
||||
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Printf("%s\n", banner)
|
||||
}
|
||||
ct.ResetColor()
|
||||
|
||||
// Create prompt for VPN type
|
||||
prompt := ui.Select{
|
||||
Label: "Select Mullvad VPN type",
|
||||
Items: []string{"WireGuard", "OpenVPN", "Cancel"},
|
||||
}
|
||||
_, promptResult, err := prompt.Run()
|
||||
|
||||
if err != nil {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Println("\n[!] User prompt failed", err)
|
||||
return
|
||||
} else if promptResult == "Cancel" {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Printf("[!] Server test canceled by user, exiting...\n")
|
||||
os.Exit(0)
|
||||
} else {
|
||||
ct.ChangeColor(ct.Cyan, true, ct.None, false)
|
||||
fmt.Printf("[*] You selected %q servers for testing\n\n", promptResult)
|
||||
}
|
||||
|
||||
ct.ChangeColor(ct.Cyan, false, ct.None, false)
|
||||
fmt.Println("[*] Retrieving list of Mullvad servers...")
|
||||
|
||||
// Make API call for server list
|
||||
response, err := http.Get(URL)
|
||||
|
||||
if err != nil {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Println("[!] Request Error\n", err.Error())
|
||||
}
|
||||
|
||||
// Wait until the response finished
|
||||
defer response.Body.Close()
|
||||
|
||||
// Read the io return of the response
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Println("[!] Error in response body\n", err.Error())
|
||||
}
|
||||
|
||||
// Unmarshal the JSON body return
|
||||
jsonErr := json.Unmarshal(body, &servers)
|
||||
if jsonErr != nil {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Println("[!] Json Error", jsonErr)
|
||||
}
|
||||
|
||||
ct.ChangeColor(ct.Green, false, ct.None, false)
|
||||
fmt.Printf("[*] Retrieved %d %s servers\n", len(servers), promptResult)
|
||||
|
||||
// Add the active servers to a list
|
||||
for i := range servers {
|
||||
servers[i].URL = servers[i].Hostname + ".mullvad.net"
|
||||
if servers[i].Type == strings.ToLower(promptResult) && servers[i].Active {
|
||||
active = append(active, servers[i])
|
||||
}
|
||||
}
|
||||
|
||||
ct.ChangeColor(ct.Green, false, ct.None, false)
|
||||
fmt.Printf("[*] There are %d active servers\n\n", len(active))
|
||||
|
||||
// Test the valid servers.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(active) - 1)
|
||||
|
||||
for _, server := range active {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
go func(wg *sync.WaitGroup, server *Server) {
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err := server.Ping()
|
||||
if err != nil {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Println("[!] Error on ping", server.URL, err)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}(&wg, server)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Printf("\n\n%s\n", banner)
|
||||
ct.ChangeColor(ct.Magenta, false, ct.None, false)
|
||||
fmt.Println("==========================================")
|
||||
fmt.Println("============ TOP 10 RESULTS ==============")
|
||||
fmt.Println("==========================================")
|
||||
ct.ResetColor()
|
||||
|
||||
sort.Sort(ByLast(active))
|
||||
sort.Sort(SortedSlice(active))
|
||||
printed := 0
|
||||
for _, server := range active {
|
||||
if server.Last < 1 {
|
||||
@ -202,3 +39,44 @@ func main() {
|
||||
printed++
|
||||
}
|
||||
}
|
||||
|
||||
// main should be generally as small as possible, in Go we like to break things out
|
||||
func main() {
|
||||
printBanner()
|
||||
|
||||
var (
|
||||
err error
|
||||
userChoice string
|
||||
)
|
||||
|
||||
if userChoice, err = interactivePrompt(); err != nil {
|
||||
cPrintln(ct.Red, "[!] UI Error\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
servers := getServers()
|
||||
|
||||
cPrintln(ct.Green, "[*] Retrieved %d %s servers\n", len(servers), userChoice)
|
||||
|
||||
// Add the active servers to a slice
|
||||
var active []*Server
|
||||
for i := range servers {
|
||||
servers[i].URL = servers[i].Hostname + ".mullvad.net"
|
||||
if servers[i].Type == strings.ToLower(userChoice) && servers[i].Active {
|
||||
active = append(active, servers[i])
|
||||
}
|
||||
}
|
||||
|
||||
if len(active) < 1 {
|
||||
cPrintln(ct.Red, "[!] No active VPN endpoints found!")
|
||||
return
|
||||
}
|
||||
|
||||
cPrintln(ct.Green, "[*] There are %d active servers\n\n", len(active))
|
||||
|
||||
// launches ping waitgroups and stores results into our pointers to the substantiated Server types
|
||||
testServers(active)
|
||||
|
||||
// because active is a slice of pointers we are able to continue to use it throughout different functions
|
||||
printResults(active)
|
||||
}
|
||||
|
113
cmd/mullfast/servers.go
Normal file
113
cmd/mullfast/servers.go
Normal file
@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
ct "github.com/daviddengcn/go-colortext"
|
||||
"github.com/nuttapp/pinghist/ping"
|
||||
)
|
||||
|
||||
// URL is the base Mullvad API endpoint.
|
||||
const URL = "https://api.mullvad.net/www/relays/all"
|
||||
|
||||
// Server struct
|
||||
type Server struct {
|
||||
List []float64
|
||||
Last float64
|
||||
URL string
|
||||
Hostname string
|
||||
CountryCode string
|
||||
CountryName string
|
||||
CityName string
|
||||
Active bool
|
||||
Type string
|
||||
}
|
||||
|
||||
// Ping server to determine latency
|
||||
func (s *Server) Ping() (float64, error) {
|
||||
// for i := 0; i < 3; i++ {
|
||||
cPrintln(ct.Cyan, "[*] Testing server:", s.URL)
|
||||
|
||||
r, err := ping.Ping(s.URL)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if r.Time < 1 {
|
||||
return -1, errors.New("presumably invalid sub 1ms latency")
|
||||
}
|
||||
|
||||
s.List = append(s.List, r.Time)
|
||||
s.Last = r.Time
|
||||
return s.Last, nil
|
||||
}
|
||||
|
||||
func getServers() []*Server {
|
||||
var (
|
||||
response *http.Response
|
||||
servers []*Server
|
||||
err error
|
||||
)
|
||||
// Make API call for server list
|
||||
response, err = http.Get(URL)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
cPrintln(ct.Red, "[!] Request Error\n", err.Error())
|
||||
os.Exit(1)
|
||||
case response == nil:
|
||||
cPrintln(ct.Red, "[!] Empty response from API\n")
|
||||
os.Exit(1)
|
||||
default:
|
||||
}
|
||||
|
||||
// Wait until the response finished
|
||||
defer response.Body.Close()
|
||||
|
||||
// Read the io return of the response
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
cPrintln(ct.Red, "[!] Error in response body\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Unmarshal the JSON body return
|
||||
jsonErr := json.Unmarshal(body, &servers)
|
||||
if jsonErr != nil {
|
||||
cPrintln(ct.Red, "[!] Json Error", jsonErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func testServers(active []*Server) {
|
||||
// Test the valid servers.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(active) - 1)
|
||||
|
||||
for _, server := range active {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
go func(wg *sync.WaitGroup, server *Server) {
|
||||
// note: you might want to carry on with all 3 tries even if it doesn't error
|
||||
// this would give you more data to go off of
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err := server.Ping()
|
||||
if err != nil {
|
||||
println(ct.Red, "[!] Error on ping", server.URL, err)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
wg.Done()
|
||||
}(&wg, server)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
13
cmd/mullfast/slice.go
Normal file
13
cmd/mullfast/slice.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
// SortedSlice list a slice of pointers to Server types.
|
||||
type SortedSlice []*Server
|
||||
|
||||
// Len returns the length of our slice.
|
||||
func (s SortedSlice) Len() int { return len(s) }
|
||||
|
||||
// Swap swaps two values placements within the slice.
|
||||
func (s SortedSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// Less method
|
||||
func (s SortedSlice) Less(i, j int) bool { return s[i].Last < s[j].Last }
|
58
cmd/mullfast/ui.go
Normal file
58
cmd/mullfast/ui.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
ct "github.com/daviddengcn/go-colortext"
|
||||
ui "github.com/manifoldco/promptui"
|
||||
)
|
||||
|
||||
// Banner contains our encoded banner.
|
||||
const Banner = "ICAgKiAgICAgICAgICAgICAgICAgICgKICggIGAgICAgICAgICAgKCAgICggIClcICkgICAgICAgICAgICAgICkKIClcKSkoICAgICAoICAgKVwgIClcKCgpLyggICAgKSAgICAgICggLygKKChfKSgpXCAgICkpXCAoKF8pKChfKS8oXykpKCAvKCAgKCAgIClcKCkpCihfKCkoKF8pIC8oKF8pIF8gICBfIChfKSlffCkoXykpIClcIChfKSkvCnwgIFwvICB8KF8pKSggfCB8IHwgfHwgfF8gKChfKV8gKChfKXwgfF8KfCB8XC98IHx8IHx8IHx8IHwgfCB8fCBfX3wvIF9gIHwoXy08fCAgX3wKfF98ICB8X3wgXF8sX3x8X3wgfF98fF98ICBcX18sX3wvX18vIFxfX3wK"
|
||||
|
||||
func banner() string {
|
||||
// Decode banner
|
||||
// this is a constant so we can safely assume no error here
|
||||
b, _ := base64.RawStdEncoding.DecodeString(Banner)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func cPrintln(color ct.Color, s string, i ...interface{}) {
|
||||
ct.ChangeColor(color, false, ct.None, false)
|
||||
defer ct.ResetColor()
|
||||
fmt.Println(s, i)
|
||||
}
|
||||
|
||||
func printBanner() {
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Printf("%s\n", banner())
|
||||
ct.ResetColor()
|
||||
}
|
||||
|
||||
func interactivePrompt() (promptResult string, err error) {
|
||||
// Create prompt for VPN type
|
||||
prompt := ui.Select{
|
||||
Label: "Select Mullvad VPN type",
|
||||
Items: []string{"WireGuard", "OpenVPN", "Cancel"},
|
||||
}
|
||||
_, promptResult, err = prompt.Run()
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Println("\n[!] User prompt failed", err)
|
||||
return
|
||||
case promptResult == "Cancel":
|
||||
ct.ChangeColor(ct.Red, false, ct.None, false)
|
||||
fmt.Printf("[!] Server test canceled by user, exiting...\n")
|
||||
return
|
||||
default:
|
||||
ct.ChangeColor(ct.Cyan, true, ct.None, false)
|
||||
fmt.Printf("[*] You selected %q servers for testing\n\n", promptResult)
|
||||
}
|
||||
|
||||
ct.ChangeColor(ct.Cyan, false, ct.None, false)
|
||||
fmt.Printf("[*] Retrieving list of %s servers from Mullvad...\n", promptResult)
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user