This repository has been archived on 2023-06-10. You can view files and clone it, but cannot push or open issues or pull requests.

127 lines
3.5 KiB
Go
Raw Normal View History

2022-03-06 15:55:58 +01:00
package main
import (
"encoding/json"
"flag"
"fmt"
2022-03-06 15:55:58 +01:00
"io"
"io/ioutil"
"net/http"
"runtime"
"strings"
2022-03-06 15:55:58 +01:00
"time"
2022-03-07 20:03:32 +01:00
"github.com/go-ping/ping"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
2022-03-06 15:55:58 +01:00
)
func main() {
2022-03-06 19:06:59 +01:00
var outputFlag = flag.String("o", "", "Output format. 'json' outputs server json")
var countryFlag = flag.String("c", "ch", "Server country code, e.g. ch for Switzerland")
2022-03-06 19:06:59 +01:00
var typeFlag = flag.String("t", "wireguard", "Server type, e.g. wireguard")
2022-03-12 11:24:27 +01:00
var logLevel = flag.String("l", "info", "Log level. Allowed values: trace, debug, info, warn, error, fatal, panic")
flag.Parse()
2022-03-12 11:24:27 +01:00
level, err := zerolog.ParseLevel(*logLevel)
if err != nil {
log.Fatal().Err(err).Msg("Unable to set log level")
}
zerolog.SetGlobalLevel(level)
2022-03-06 19:06:59 +01:00
servers := getServers(*typeFlag)
bestIndex := selectBestServerIndex(servers, *countryFlag)
2022-03-12 11:45:05 +01:00
if bestIndex == -1 {
log.Fatal().Str("country", *countryFlag).Msg("No servers for country found.")
}
2022-03-06 19:06:59 +01:00
best := servers[bestIndex]
log.Debug().Interface("server", best).Msg("Best latency server found.")
hostname := strings.TrimSuffix(best.Hostname, "-wireguard")
if *outputFlag != "json" {
fmt.Println(hostname)
} else {
2022-03-06 19:06:59 +01:00
serverJson, err := json.Marshal(best)
if err != nil {
log.Fatal().Err(err).Msg("Couldn't marshal server information to Json")
}
fmt.Println(string(serverJson))
}
2022-03-06 15:55:58 +01:00
}
func selectBestServerIndex(servers []server, country string) int {
bestIndex := -1
2022-03-07 19:24:11 +01:00
var bestPing time.Duration
2022-03-06 15:55:58 +01:00
for i, server := range servers {
if server.Active && server.CountryCode == country {
2022-03-06 15:55:58 +01:00
duration, err := serverLatency(server)
if err == nil {
2022-03-07 19:24:11 +01:00
if bestIndex == -1 || bestPing > duration {
2022-03-06 15:55:58 +01:00
bestIndex = i
2022-03-07 19:24:11 +01:00
bestPing = duration
2022-03-06 15:55:58 +01:00
}
} else {
log.Err(err).Msg("Error determining the server latency via ping.")
2022-03-06 15:55:58 +01:00
}
}
}
return bestIndex
}
2022-03-06 19:06:59 +01:00
func getServers(serverType string) []server {
resp, err := http.Get("https://api.mullvad.net/www/relays/" + serverType + "/")
2022-03-06 15:55:58 +01:00
if err != nil {
log.Fatal().Err(err).Msg("Couldn't retrieve servers")
2022-03-06 15:55:58 +01:00
}
responseBody, err := ioutil.ReadAll(resp.Body)
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
log.Err(err)
}
}(resp.Body)
2022-03-07 20:03:32 +01:00
if err != nil {
log.Fatal().Err(err)
}
2022-03-06 15:55:58 +01:00
var servers []server
err = json.Unmarshal(responseBody, &servers)
if err != nil {
log.Fatal().Err(err).Msg("couldn't unmarshall server json")
2022-03-06 15:55:58 +01:00
}
return servers
}
//goland:noinspection GoBoolExpressions
2022-03-06 15:55:58 +01:00
func serverLatency(s server) (time.Duration, error) {
pinger, err := ping.NewPinger(s.Ipv4AddrIn)
if runtime.GOOS == "windows" {
pinger.SetPrivileged(true)
}
2022-03-06 15:55:58 +01:00
pinger.Count = 1
if err != nil {
return 0, err
}
var duration time.Duration
pinger.OnRecv = func(pkt *ping.Packet) {
log.Debug().Str("Server", s.Hostname).IPAddr("IP", pkt.IPAddr.IP).Dur("RTT", pkt.Rtt).Msg("Added server latency.")
2022-03-06 15:55:58 +01:00
duration = pkt.Rtt
}
err = pinger.Run()
return duration, err
}
type server struct {
Hostname string `json:"hostname"`
CountryCode string `json:"country_code"`
CountryName string `json:"country_name"`
CityCode string `json:"city_code"`
CityName string `json:"city_name"`
Active bool `json:"active"`
Owned bool `json:"owned"`
Provider string `json:"provider"`
Ipv4AddrIn string `json:"ipv4_addr_in"`
Ipv6AddrIn string `json:"ipv6_addr_in"`
NetworkPortSpeed int `json:"network_port_speed"`
Pubkey string `json:"pubkey"`
MultihopPort int `json:"multihop_port"`
SocksName string `json:"socks_name"`
}