Compare commits

..

No commits in common. "410cc7874d64d867264fd4f2f16e0ee68155fa12" and "7f736db2cc1cf3d00970d5de21cadb3cf1532a5b" have entirely different histories.

7 changed files with 29 additions and 15389 deletions

View File

@ -5,12 +5,3 @@ build:
run: run:
sudo go run ./cmd/mullvadclosest/*.go sudo go run ./cmd/mullvadclosest/*.go
test:
go test -v ./...
fmt:
go fmt ./...
lint:
golangci-lint run

View File

@ -2,9 +2,8 @@ package main
import ( import (
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io" "io/ioutil"
"math" "math"
"math/rand" "math/rand"
"net/http" "net/http"
@ -16,9 +15,6 @@ import (
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
) )
// Package-level variable for debug flag
var debugMode bool
// RelayLatency holds a relay and its associated latency // RelayLatency holds a relay and its associated latency
type RelayLatency struct { type RelayLatency struct {
Relay Relay Relay Relay
@ -50,7 +46,7 @@ func livenessWorker(id int, jobs <-chan Relay, results chan<- RelayLatency, wg *
if err == nil && isLive { if err == nil && isLive {
results <- RelayLatency{Relay: relay, Latency: latency} results <- RelayLatency{Relay: relay, Latency: latency}
} }
_ = bar.Add(1) bar.Add(1)
} }
} }
@ -64,10 +60,8 @@ func latencyWorker(id int, jobs <-chan RelayLatency, results chan<- RelayLatency
Relay: relayLatency.Relay, Relay: relayLatency.Relay,
Latency: latency, Latency: latency,
} }
} else if debugMode {
fmt.Printf("Debug: Error measuring latency for %s: %v\n", relayLatency.Relay.Hostname, err)
} }
_ = bar.Add(1) bar.Add(1)
} }
} }
@ -186,14 +180,13 @@ func PrintRelayLatencies(relayLatencies []RelayLatency, totalRelays int, deadRel
fmt.Println() fmt.Println()
} }
fmt.Printf("%-20s %-20s %-30s %-15s %s\n", "Country", "City", "Hostname", "IPv4 Address", "Latency") fmt.Printf("%-20s %-20s %-30s %s\n", "Country", "City", "Hostname", "Latency")
for i, rl := range relayLatencies { for i, rl := range relayLatencies {
if i < 25 || i >= len(relayLatencies)-4 { if i < 25 || i >= len(relayLatencies)-4 {
fmt.Printf("%-20s %-20s %-30s %-15s %v\n", fmt.Printf("%-20s %-20s %-30s %v\n",
rl.Relay.Location.Country, rl.Relay.Location.Country,
rl.Relay.Location.City, rl.Relay.Location.City,
rl.Relay.Hostname, rl.Relay.Hostname,
rl.Relay.Ipv4AddrIn,
rl.Latency) rl.Latency)
} }
if i == 24 { if i == 24 {
@ -210,7 +203,7 @@ func CheckMullvadExitIP() (bool, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to read response body: %v", err) return false, fmt.Errorf("failed to read response body: %v", err)
} }
@ -224,14 +217,6 @@ func CheckMullvadExitIP() (bool, error) {
} }
func main() { func main() {
// Define force flag
force := flag.Bool("force", false, "Force execution even when connected to VPN")
debug := flag.Bool("debug", false, "Enable debug output")
flag.Parse()
// Set the global debug mode
debugMode = *debug
currentUser, err := user.Current() currentUser, err := user.Current()
if err != nil { if err != nil {
panic("Failed to get current user") panic("Failed to get current user")
@ -246,9 +231,8 @@ func main() {
panic(fmt.Sprintf("Error checking Mullvad exit IP: %v", err)) panic(fmt.Sprintf("Error checking Mullvad exit IP: %v", err))
} }
if isMullvadExitIP && !*force { if isMullvadExitIP {
fmt.Println("This program is designed to test latency between your actual IP and the Mullvad VPN servers. Please disconnect from the VPN and run the program again.") fmt.Println("This program is designed to test latency between your actual IP and the Mullvad VPN servers. Please disconnect from the VPN and run the program again.")
fmt.Println("Use -force flag to bypass this check.")
return return
} }

View File

@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"time" "time"
@ -45,21 +46,11 @@ func (r Relay) CheckLiveness() (bool, time.Duration, error) {
pinger.Count = 1 pinger.Count = 1
pinger.Timeout = 3 * time.Second // Increased timeout for the single ping pinger.Timeout = 3 * time.Second // Increased timeout for the single ping
pinger.SetPrivileged(true) pinger.SetPrivileged(true)
if err := pinger.Run(); err != nil { pinger.Run()
return false, 0, err
}
stats := pinger.Statistics() stats := pinger.Statistics()
if stats.PacketsRecv == 0 { if stats.PacketsRecv == 0 {
return false, 0, nil return false, 0, nil
} }
// Validate that we don't have a zero latency for a successful ping
if stats.AvgRtt == 0 {
// Return as live but with no initial latency measurement
// The actual latency will be measured in MeasureLatency
return true, time.Duration(1), nil
}
return true, stats.AvgRtt, nil return true, stats.AvgRtt, nil
} }
@ -73,28 +64,8 @@ func (r Relay) MeasureLatency() (time.Duration, error) {
pinger.Interval = 1 * time.Second // Adding interval between pings pinger.Interval = 1 * time.Second // Adding interval between pings
pinger.Timeout = 10 * time.Second // Increased overall timeout pinger.Timeout = 10 * time.Second // Increased overall timeout
pinger.SetPrivileged(true) pinger.SetPrivileged(true)
if err := pinger.Run(); err != nil { pinger.Run()
return 0, err
}
stats := pinger.Statistics() stats := pinger.Statistics()
// Debug output
if debugMode && (stats.MinRtt == 0 || stats.AvgRtt == 0) {
fmt.Printf("Debug: %s (%s) - PacketsSent: %d, PacketsRecv: %d, MinRtt: %v, AvgRtt: %v, MaxRtt: %v\n",
r.Hostname, r.Ipv4AddrIn, stats.PacketsSent, stats.PacketsRecv,
stats.MinRtt, stats.AvgRtt, stats.MaxRtt)
}
// Validate the statistics - if MinRtt is 0 but we received packets, something is wrong
if stats.PacketsRecv > 0 && stats.MinRtt == 0 {
// Use average RTT if available and non-zero
if stats.AvgRtt > 0 {
return stats.AvgRtt, nil
}
// If all RTT values are 0, this is likely a bug - return an error
return 0, fmt.Errorf("invalid zero latency for relay %s", r.Hostname)
}
return stats.MinRtt, nil return stats.MinRtt, nil
} }
@ -139,7 +110,7 @@ func ParseRelayData() ([]Relay, error) {
return nil, fmt.Errorf("failed to read embedded file: %v", err) return nil, fmt.Errorf("failed to read embedded file: %v", err)
} }
} else { } else {
fileData, err = os.ReadFile(filePath) fileData, err = ioutil.ReadFile(filePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read file: %v", err) return nil, fmt.Errorf("failed to read file: %v", err)
} }

View File

@ -4,7 +4,7 @@ import (
"embed" "embed"
) )
//go:embed relays.2025-07-14.json //go:embed relays.2024-06-04.json
var fs embed.FS var fs embed.FS
func GetEmbeddedFile(name string) ([]byte, error) { func GetEmbeddedFile(name string) ([]byte, error) {
@ -12,5 +12,5 @@ func GetEmbeddedFile(name string) ([]byte, error) {
} }
func GetRelays() ([]byte, error) { func GetRelays() ([]byte, error) {
return GetEmbeddedFile("relays.2025-07-14.json") return GetEmbeddedFile("relays.2024-06-04.json")
} }

17
go.mod
View File

@ -1,20 +1,15 @@
module sneak.berlin/go/mullvadclosest module sneak.berlin/go/mullvadclosest
go 1.23.0 go 1.22.3
toolchain go1.24.4
require (
github.com/go-ping/ping v1.1.0
github.com/schollz/progressbar/v3 v3.14.3
)
require ( require (
github.com/go-ping/ping v1.1.0 // indirect
github.com/google/uuid v1.2.0 // indirect github.com/google/uuid v1.2.0 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/net v0.42.0 // indirect github.com/schollz/progressbar/v3 v3.14.3 // indirect
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.33.0 // indirect golang.org/x/term v0.20.0 // indirect
) )

10
go.sum
View File

@ -1,5 +1,4 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
@ -9,32 +8,25 @@ github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.14.3 h1:oOuWW19ka12wxYU1XblR4n16wF/2Y1dBLMarMo6p4xU= github.com/schollz/progressbar/v3 v3.14.3 h1:oOuWW19ka12wxYU1XblR4n16wF/2Y1dBLMarMo6p4xU=
github.com/schollz/progressbar/v3 v3.14.3/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI= github.com/schollz/progressbar/v3 v3.14.3/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 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-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 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
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/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=

File diff suppressed because it is too large Load Diff