Compare commits

...

7 Commits

3 changed files with 46 additions and 30 deletions

View File

@ -14,6 +14,7 @@ builds:
- linux - linux
- windows - windows
- darwin - darwin
mod_timestamp: "{{ .CommitTimestamp }}"
archives: archives:
- replacements: - replacements:
darwin: Darwin darwin: Darwin
@ -21,6 +22,8 @@ archives:
windows: Windows windows: Windows
386: i386 386: i386
amd64: x86_64 amd64: x86_64
format: binary
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
snapshot: snapshot:

View File

@ -1,7 +1,7 @@
# mullvad-best-server # mullvad-best-server
![Build](https://github.com/bastiandoetsch/mullvad-best-server/actions/workflows/go.yml/badge.svg) ![Build](https://github.com/bastiandoetsch/mullvad-best-server/actions/workflows/go.yml/badge.svg)
Determines the mullvat.net wireguard server with the lowest latency. Determines the mullvad.net server with the lowest latency.
## Installation ## Installation
@ -11,12 +11,34 @@ Download binary from releases for your platform and unpack.
### Default usage ### Default usage
Execute `mullvad-best-server`. It outputs the code, e.g. `de05`. You can then connect to it with e.g. wireguard using the normal shell scripts. Execute `mullvad-best-server`. It outputs the code, e.g. `de05`. You can then connect to it with e.g. wireguard using the normal shell scripts.
### Extended Output ### Command line parameters
If you want the full server information, execute `mullvad-best-server -o json`. It returns the full json output of the server information.
```angular2html
Usage of dist/mullvad-best-server_darwin_amd64/mullvad-best-server:
-c string
Server country code, e.g. ch for Switzerland (default "ch")
-o string
Output format. 'json' outputs server json
-t string
Server type, e.g. wireguard (default "wireguard")
```
If you want the full server information, execute `mullvad-best-server -o json`. It returns the full json output of the server information.
The `-c` flag allows to give a country code. Else `ch` will be used.
### Specify Country
The -c flag allows to give a country code. Else de/at/ch will be used.
## Background ## Background
The program uses `https://api.mullvad.net/www/relays/wireguard/` to get the current server list, pings the ones with the right country The program uses `https://api.mullvad.net/www/relays/<SERVER_TYPE/` to get the current server list, pings the ones with the right country
and outputs the server with the lowest ping. and outputs the server with the lowest ping.
## Integration into a script
I use it on my router like this (yes, I know I could have done the whole thing with jq and shell scripting, but wanted to use go for maintainability).
```
#!/bin/sh
set -e
LATEST_RELEASE=$(curl -sSL https://api.github.com/repos/bastiandoetsch/mullvad-best-server/releases/latest | jq -r '.assets[]| .browser_download_url' | grep Linux_arm64)
curl -sSL $LATEST_RELEASE > /root/mullvad-best-server
chmod +x /root/mullvad-best-server
/usr/bin/wg-quick down $(wg show|grep interface | cut -d: -f2) || echo "nothing to shut down"
/usr/bin/wg-quick up "mullvad-$(/root/mullvad-best-server -c de)"
```

39
main.go
View File

@ -14,22 +14,22 @@ import (
"time" "time"
) )
var pings = make(map[string]time.Duration)
func main() { func main() {
zerolog.SetGlobalLevel(zerolog.InfoLevel) zerolog.SetGlobalLevel(zerolog.InfoLevel)
var outputFlag = flag.String("o", "short", "Output format. 'json' outputs server json") 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") var countryFlag = flag.String("c", "ch", "Server country code, e.g. ch for Switzerland")
var typeFlag = flag.String("t", "wireguard", "Server type, e.g. wireguard")
flag.Parse() flag.Parse()
servers := getServers() servers := getServers(*typeFlag)
bestIndex := selectBestServerIndex(servers, countryFlag) bestIndex := selectBestServerIndex(servers, *countryFlag)
log.Debug().Interface("server", servers[bestIndex]).Msg("Best latency server found.") best := servers[bestIndex]
hostname := strings.Split(servers[bestIndex].Hostname, "-")[0] log.Debug().Interface("server", best).Msg("Best latency server found.")
hostname := strings.TrimSuffix(best.Hostname, "-wireguard")
if *outputFlag != "json" { if *outputFlag != "json" {
fmt.Println(hostname) fmt.Println(hostname)
} else { } else {
serverJson, err := json.Marshal(servers[bestIndex]) serverJson, err := json.Marshal(best)
if err != nil { if err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }
@ -37,25 +37,16 @@ func main() {
} }
} }
func selectBestServerIndex(servers []server, country *string) int { func selectBestServerIndex(servers []server, country string) int {
best := servers[0].Hostname
bestIndex := -1 bestIndex := -1
allowedCountries := map[string]string{} var bestPing time.Duration
if *country == "" {
allowedCountries["de"] = "1"
allowedCountries["ch"] = "1"
allowedCountries["at"] = "1"
} else {
allowedCountries[*country] = "1"
}
for i, server := range servers { for i, server := range servers {
if server.Active && allowedCountries[server.CountryCode] != "" { if server.Active && server.CountryCode == country {
duration, err := serverLatency(server) duration, err := serverLatency(server)
if err == nil { if err == nil {
pings[server.Hostname] = duration if bestIndex == -1 || bestPing > duration {
if bestIndex == -1 || pings[best] > pings[server.Hostname] {
best = server.Hostname
bestIndex = i bestIndex = i
bestPing = duration
} }
} }
} }
@ -63,8 +54,8 @@ func selectBestServerIndex(servers []server, country *string) int {
return bestIndex return bestIndex
} }
func getServers() []server { func getServers(serverType string) []server {
resp, err := http.Get("https://api.mullvad.net/www/relays/wireguard/") resp, err := http.Get("https://api.mullvad.net/www/relays/" + serverType + "/")
if err != nil { if err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }