Compare commits

..

27 Commits

Author SHA1 Message Date
8aedb82176 added window information to the README.md 2022-07-28 20:32:43 -05:00
91ba7a4dfa fix #4 by sending a priviledged raw ICMP ping 2022-07-28 20:19:49 -05:00
2b05df2b75 Fix some error logging - Msg() needs to be called for exiting with exit 1 2022-03-19 17:00:48 +01:00
415cfa8ee7 recover gracefully if no server found 2022-03-12 11:45:41 +01:00
0bf9fc463a Update README.md 2022-03-12 11:28:58 +01:00
44cfbce5f1 add flag -l to set log-level 2022-03-12 11:24:27 +01:00
3c17eded13 remove redundant '-' from .goreleaser.yaml 2022-03-07 21:45:27 +01:00
d3a3969aee remove build folder after linting to enable clean state repo for goreleaser 2022-03-07 21:38:48 +01:00
7f01d016a4 remove linter binary after linting to enable clean state repo for goreleaser 2022-03-07 20:09:48 +01:00
5f3f234fff add linter and make file (#2) 2022-03-07 20:03:32 +01:00
18f5d74402 optimize build 2022-03-07 19:26:58 +01:00
71611fd754 simplify & optimize code 2022-03-07 19:24:11 +01:00
310f59e03c describe usage in script 2022-03-06 19:57:31 +01:00
a6992b2373 archive binaries without version number, use commit timestamp as mod_timestamp 2022-03-06 19:31:40 +01:00
31859569fc fix typo in README 2022-03-06 19:25:06 +01:00
d31571fc50 cleanup (country default now comes from flag) 2022-03-06 19:20:21 +01:00
5eefe15b3b Update README.md 2022-03-06 19:10:33 +01:00
dbb84f0c0c add support to select server type 2022-03-06 19:06:59 +01:00
6c839d7196 Create LICENSE (#1) 2022-03-06 18:33:24 +01:00
d2de3225b0 add support for selecting a country via CLI flag 2022-03-06 18:03:36 +01:00
130a048394 fix design after adding badge in README.md 2022-03-06 17:47:47 +01:00
0be7f66582 updated README.md with badge 2022-03-06 17:46:55 +01:00
0f014cf9f3 updated README.md with usage information 2022-03-06 17:37:44 +01:00
230e89e2b7 allow full json output for best server using flag -o json 2022-03-06 17:36:21 +01:00
20e1092604 unshallow checkout to always have access to the latest tag during release 2022-03-06 17:16:09 +01:00
2998e1d39d Create README.md 2022-03-06 17:09:40 +01:00
5821d890b8 only give short server (e.g. de5) as output 2022-03-06 16:51:45 +01:00
9 changed files with 264 additions and 69 deletions

View File

@ -9,6 +9,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
@ -18,6 +20,12 @@ jobs:
- name: Test - name: Test
run: go test -v ./... run: go test -v ./...
- name: Lint source code
run: |
make tools lint
rm -rf .bin/
rm -rf dist/
- name: Create release tag - name: Create release tag
run: | run: |
git tag "v$(git show -s --format=%cd --date=format:%Y%m%d.%H%M%S)" git tag "v$(git show -s --format=%cd --date=format:%Y%m%d.%H%M%S)"

26
.golangci.yaml Normal file
View File

@ -0,0 +1,26 @@
linters:
disable-all: true
enable:
- deadcode
- errcheck
- gofmt
- goimports
- gosimple
- ineffassign
- misspell
- staticcheck
- structcheck
- unconvert
- unused
- varcheck
- govet
linters-settings:
goimports:
local-prefixes: github.com/bastiandoetsch/mullvad-best-server
output:
format: tab
run:
deadline: 10m

View File

@ -1,19 +1,20 @@
project_name: mullvad-best-server project_name: mullvad-best-server
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before: before:
hooks: hooks:
# You may remove this if you don't use go modules.
- go mod tidy - go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds: builds:
- env: - flags:
- -trimpath
env:
- CGO_ENABLED=0 - CGO_ENABLED=0
goos: goos:
- linux - linux
- windows - windows
- darwin - darwin
mod_timestamp: "{{ .CommitTimestamp }}"
archives: archives:
- replacements: - replacements:
darwin: Darwin darwin: Darwin
@ -21,10 +22,15 @@ 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:
name_template: "{{ incpatch .Version }}-next" name_template: "{{ incpatch .Version }}-next"
changelog: changelog:
sort: asc sort: asc
filters: filters:

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Bastian Doetsch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

60
Makefile Normal file
View File

@ -0,0 +1,60 @@
# project variables
PROJECT_NAME := mullvad-best-server
# build variables
.DEFAULT_GOAL = lint
BUILD_DIR := dist
DEV_GOARCH := $(shell go env GOARCH)
DEV_GOOS := $(shell go env GOOS)
## tools: Install required tooling.
.PHONY: tools
tools:
ifeq (,$(wildcard ./.bin/golangci-lint*))
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b .bin/ v1.44.2
else
@echo "==> Required tooling is already installed"
endif
## clean: Delete the build directory
.PHONY: clean
clean:
@echo "==> Removing '$(BUILD_DIR)' directory..."
@rm -rf $(BUILD_DIR)
## lint: Lint code with golangci-lint.
.PHONY: lint
lint: tools
@echo "==> Linting code with 'golangci-lint'..."
@.bin/golangci-lint run ./...
## test: Run all unit tests.
.PHONY: test
test:
@echo "==> Running unit tests..."
@mkdir -p $(BUILD_DIR)
@go test -count=1 -v -cover -coverprofile=$(BUILD_DIR)/coverage.out -parallel=4 ./...
## build: Build binary for default local system's OS and architecture.
.PHONY: build
build:
@echo "==> Building binary..."
@echo " running go build for GOOS=$(DEV_GOOS) GOARCH=$(DEV_GOARCH)"
# workaround for missing .exe extension on Windows
ifeq ($(OS),Windows_NT)
@go build -o $(BUILD_DIR)/$(PROJECT_NAME).$(DEV_GOOS).$(DEV_GOARCH).exe
else
@go build -o $(BUILD_DIR)/$(PROJECT_NAME).$(DEV_GOOS).$(DEV_GOARCH)
endif
.PHONY: run
run:
@echo "==> Running $(PROJECT_NAME)"
@go run main.go
help: Makefile
@echo "Usage: make <command>"
@echo ""
@echo "Commands:"
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'

51
README.md Normal file
View File

@ -0,0 +1,51 @@
# mullvad-best-server
![Build](https://github.com/bastiandoetsch/mullvad-best-server/actions/workflows/go.yml/badge.svg)
Determines the mullvad.net server with the lowest latency on macOS and Linux. On Windows, it can only check if the server is up.
The reason lies with the golang network libraries, according to the `go-ping` library, that is used under the hood for pinging:
```
Please note that accessing packet TTL values is not supported due to limitations in the Go x/net/ipv4 and x/net/ipv6 packages.
```
## Installation
Download binary from releases for your platform and unpack.
## 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.
### Command line parameters
```angular2html
Usage of dist/mullvad-best-server_darwin_amd64/mullvad-best-server:
-c string
Server country code, e.g. ch for Switzerland (default "ch")
-l string
Log level. Allowed values: trace, debug, info, warn, error, fatal, panic (default "info")
-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.
## Background
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.
## 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)"
```

14
go.mod
View File

@ -3,13 +3,15 @@ module github.com/bastiandoetsch/mullvad-best-server
go 1.17 go 1.17
require ( require (
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 github.com/go-ping/ping v1.1.0
github.com/rs/zerolog v1.26.1 github.com/rs/zerolog v1.27.0
) )
require ( require (
github.com/google/uuid v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect github.com/mattn/go-colorable v0.1.12 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
) )

52
go.sum
View File

@ -1,41 +1,33 @@
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

79
main.go
View File

@ -2,52 +2,74 @@ package main
import ( import (
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"github.com/go-ping/ping"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"runtime"
"strings"
"time" "time"
"github.com/go-ping/ping"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
) )
var pings = make(map[string]time.Duration)
func main() { func main() {
zerolog.SetGlobalLevel(zerolog.InfoLevel) var outputFlag = flag.String("o", "", "Output format. 'json' outputs server json")
servers := getServers() var countryFlag = flag.String("c", "ch", "Server country code, e.g. ch for Switzerland")
bestIndex := selectBestServerIndex(servers) var typeFlag = flag.String("t", "wireguard", "Server type, e.g. wireguard")
log.Debug().Interface("server", servers[bestIndex]).Msg("Best latency server found.") var logLevel = flag.String("l", "info", "Log level. Allowed values: trace, debug, info, warn, error, fatal, panic")
fmt.Println(servers[bestIndex].Hostname) flag.Parse()
level, err := zerolog.ParseLevel(*logLevel)
if err != nil {
log.Fatal().Err(err).Msg("Unable to set log level")
}
zerolog.SetGlobalLevel(level)
servers := getServers(*typeFlag)
bestIndex := selectBestServerIndex(servers, *countryFlag)
if bestIndex == -1 {
log.Fatal().Str("country", *countryFlag).Msg("No servers for country found.")
}
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 {
serverJson, err := json.Marshal(best)
if err != nil {
log.Fatal().Err(err).Msg("Couldn't marshal server information to Json")
}
fmt.Println(string(serverJson))
}
} }
func selectBestServerIndex(servers []server) int { func selectBestServerIndex(servers []server, country string) int {
best := servers[0].Hostname bestIndex := -1
bestIndex := 0 var bestPing time.Duration
allowedCountries := map[string]string{}
allowedCountries["de"] = "1"
allowedCountries["ch"] = "1"
allowedCountries["at"] = "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 best == "" || pings[best] > pings[server.Hostname] {
best = server.Hostname
bestIndex = i bestIndex = i
bestPing = duration
} }
} else {
log.Err(err).Msg("Error determining the server latency via ping.")
} }
} }
} }
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).Msg("Couldn't retrieve servers")
} }
responseBody, err := ioutil.ReadAll(resp.Body) responseBody, err := ioutil.ReadAll(resp.Body)
defer func(Body io.ReadCloser) { defer func(Body io.ReadCloser) {
@ -56,16 +78,23 @@ func getServers() []server {
log.Err(err) log.Err(err)
} }
}(resp.Body) }(resp.Body)
if err != nil {
log.Fatal().Err(err)
}
var servers []server var servers []server
err = json.Unmarshal(responseBody, &servers) err = json.Unmarshal(responseBody, &servers)
if err != nil { if err != nil {
log.Fatal().Err(err) log.Fatal().Err(err).Msg("couldn't unmarshall server json")
} }
return servers return servers
} }
//goland:noinspection GoBoolExpressions
func serverLatency(s server) (time.Duration, error) { func serverLatency(s server) (time.Duration, error) {
pinger, err := ping.NewPinger(s.Ipv4AddrIn) pinger, err := ping.NewPinger(s.Ipv4AddrIn)
if runtime.GOOS == "windows" {
pinger.SetPrivileged(true)
}
pinger.Count = 1 pinger.Count = 1
if err != nil { if err != nil {
return 0, err return 0, err