Mullvad updater (#228)
* Add Mullvad to updater cli * Update hardcoded servers for Mullvad
This commit is contained in:
87
internal/updater/mullvad.go
Normal file
87
internal/updater/mullvad.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) findMullvadServers() (servers []models.MullvadServer, err error) {
|
||||
const url = "https://api.mullvad.net/www/relays/openvpn/"
|
||||
response, err := u.httpGet(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf(response.Status)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []struct {
|
||||
Country string `json:"country_name"`
|
||||
City string `json:"city_name"`
|
||||
Active bool `json:"active"`
|
||||
Owned bool `json:"owned"`
|
||||
Provider string `json:"provider"`
|
||||
IPv4 string `json:"ipv4_addr_in"`
|
||||
IPv6 string `json:"ipv6_addr_in"`
|
||||
}
|
||||
if err := json.Unmarshal(bytes, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serversByKey := map[string]models.MullvadServer{}
|
||||
for _, jsonServer := range data {
|
||||
if !jsonServer.Active {
|
||||
continue
|
||||
}
|
||||
ipv4 := net.ParseIP(jsonServer.IPv4)
|
||||
ipv6 := net.ParseIP(jsonServer.IPv6)
|
||||
if ipv4 == nil || ipv4.To4() == nil {
|
||||
return nil, fmt.Errorf("cannot parse ipv4 address %q", jsonServer.IPv4)
|
||||
} else if ipv6 == nil || ipv6.To4() != nil {
|
||||
return nil, fmt.Errorf("cannot parse ipv6 address %q", jsonServer.IPv6)
|
||||
}
|
||||
key := fmt.Sprintf("%s%s%t%s", jsonServer.Country, jsonServer.City, jsonServer.Owned, jsonServer.Provider)
|
||||
if server, ok := serversByKey[key]; ok {
|
||||
server.IPs = append(server.IPs, ipv4)
|
||||
server.IPsV6 = append(server.IPsV6, ipv6)
|
||||
serversByKey[key] = server
|
||||
} else {
|
||||
serversByKey[key] = models.MullvadServer{
|
||||
IPs: []net.IP{ipv4},
|
||||
IPsV6: []net.IP{ipv6},
|
||||
Country: jsonServer.Country,
|
||||
City: jsonServer.City,
|
||||
ISP: jsonServer.Provider,
|
||||
Owned: jsonServer.Owned,
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, server := range serversByKey {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Country+servers[i].City+servers[i].ISP < servers[j].Country+servers[j].City+servers[j].ISP
|
||||
})
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
//nolint:goconst
|
||||
func stringifyMullvadServers(servers []models.MullvadServer) (s string) {
|
||||
s = "func MullvadServers() []models.MullvadServer {\n"
|
||||
s += " return []models.MullvadServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
31
internal/updater/mullvad_test.go
Normal file
31
internal/updater/mullvad_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_stringifyMullvadServers(t *testing.T) {
|
||||
servers := []models.MullvadServer{{
|
||||
Country: "webland",
|
||||
City: "webcity",
|
||||
ISP: "not nsa",
|
||||
Owned: true,
|
||||
IPs: []net.IP{{1, 1, 1, 1}},
|
||||
IPsV6: []net.IP{{1, 1, 1, 1}},
|
||||
}}
|
||||
expected := `
|
||||
func MullvadServers() []models.MullvadServer {
|
||||
return []models.MullvadServer{
|
||||
{Country: "webland", City: "webcity", ISP: "not nsa", Owned: true, IPs: []net.IP{{1, 1, 1, 1}}, IPsV6: []net.IP{{1, 1, 1, 1}}},
|
||||
}
|
||||
}
|
||||
`
|
||||
expected = strings.TrimPrefix(strings.TrimSuffix(expected, "\n"), "\n")
|
||||
s := stringifyMullvadServers(servers)
|
||||
assert.Equal(t, expected, s)
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package updater
|
||||
|
||||
type Options struct {
|
||||
PIA bool
|
||||
PIAold bool
|
||||
File bool // update JSON file (user side)
|
||||
Stdout bool // update constants file (maintainer side)
|
||||
PIA bool
|
||||
PIAold bool
|
||||
Mullvad bool
|
||||
File bool // update JSON file (user side)
|
||||
Stdout bool // update constants file (maintainer side)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package updater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
@@ -16,13 +17,15 @@ type updater struct {
|
||||
storage storage.Storage
|
||||
timeNow func() time.Time
|
||||
println func(s string)
|
||||
httpGet func(url string) (resp *http.Response, err error)
|
||||
}
|
||||
|
||||
func New(storage storage.Storage) Updater {
|
||||
func New(storage storage.Storage, httpClient *http.Client) Updater {
|
||||
return &updater{
|
||||
storage: storage,
|
||||
timeNow: time.Now,
|
||||
println: func(s string) { fmt.Println(s) },
|
||||
httpGet: httpClient.Get,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +62,18 @@ func (u *updater) UpdateServers(options Options) error {
|
||||
allServers.PiaOld.Servers = servers
|
||||
}
|
||||
|
||||
if options.Mullvad {
|
||||
servers, err := u.findMullvadServers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Mullvad servers: %w", err)
|
||||
}
|
||||
if options.Stdout {
|
||||
u.println(stringifyMullvadServers(servers))
|
||||
}
|
||||
allServers.Mullvad.Timestamp = u.timeNow().Unix()
|
||||
allServers.Mullvad.Servers = servers
|
||||
}
|
||||
|
||||
if options.File {
|
||||
if err := u.storage.FlushToFile(allServers); err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user