Feat: WeVPN support (#591)
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/torguard"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/vpnunlimited"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/vyprvpn"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/wevpn"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/windscribe"
|
||||
)
|
||||
|
||||
@@ -357,6 +358,27 @@ func (u *updater) updateVyprvpn(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) updateWevpn(ctx context.Context) (err error) {
|
||||
minServers := getMinServers(len(u.servers.Wevpn.Servers))
|
||||
servers, warnings, err := wevpn.GetServers(ctx, u.presolver, minServers)
|
||||
if u.options.CLI {
|
||||
for _, warning := range warnings {
|
||||
u.logger.Warn("WeVPN: " + warning)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(u.servers.Wevpn.Servers, servers) {
|
||||
return nil
|
||||
}
|
||||
|
||||
u.servers.Wevpn.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Wevpn.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) updateWindscribe(ctx context.Context) (err error) {
|
||||
minServers := getMinServers(len(u.servers.Windscribe.Servers))
|
||||
servers, err := windscribe.GetServers(ctx, u.client, minServers)
|
||||
|
||||
76
internal/updater/providers/wevpn/cities.go
Normal file
76
internal/updater/providers/wevpn/cities.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package wevpn
|
||||
|
||||
// getAvailableCities get available cities as listed on the WeVPN website.
|
||||
func getAvailableCities() (cities []string) {
|
||||
return []string{
|
||||
"Cairo",
|
||||
"Chennai",
|
||||
"Denizli",
|
||||
"Dubai",
|
||||
"Johannesburg",
|
||||
"Lagos",
|
||||
"Tel Aviv",
|
||||
"Atlanta",
|
||||
"Buenos Aires",
|
||||
"Chicago",
|
||||
"Dallas",
|
||||
"Denver",
|
||||
"Los Angeles",
|
||||
"Los Angeles-PF",
|
||||
"Mexico City",
|
||||
"Miami",
|
||||
"Montreal",
|
||||
"New Jersey",
|
||||
"New York",
|
||||
"New York-PF",
|
||||
"Phoenix",
|
||||
"Salt Lake City",
|
||||
"San Jose",
|
||||
"Sao Paulo",
|
||||
"Seattle",
|
||||
"Toronto",
|
||||
"Vancouver",
|
||||
"Washington DC",
|
||||
"Auckland",
|
||||
"Hanoi",
|
||||
"Hong Kong",
|
||||
"Jakarta",
|
||||
"Manila",
|
||||
"Melbourne",
|
||||
"Moscow",
|
||||
"Seoul",
|
||||
"Sibu",
|
||||
"Singapore",
|
||||
"St Petersburg",
|
||||
"Sydney",
|
||||
"Taipei",
|
||||
"Tokyo",
|
||||
"Amsterdam",
|
||||
"Athens",
|
||||
"Belgrade",
|
||||
"Brussels",
|
||||
"Bucharest",
|
||||
"Budapest",
|
||||
"Copenhagen",
|
||||
"Dublin",
|
||||
"Frankfurt",
|
||||
"Helsinki",
|
||||
"Kiev",
|
||||
"Lisbon",
|
||||
"London",
|
||||
"London-PF",
|
||||
"Luxembourg",
|
||||
"Madrid",
|
||||
"Manchester",
|
||||
"Milan",
|
||||
"Oslo",
|
||||
"Oulu",
|
||||
"Paris",
|
||||
"Prague",
|
||||
"Sofia",
|
||||
"Stockholm",
|
||||
"Vienna",
|
||||
"Warsaw",
|
||||
"Zurich",
|
||||
}
|
||||
}
|
||||
24
internal/updater/providers/wevpn/hostname.go
Normal file
24
internal/updater/providers/wevpn/hostname.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package wevpn
|
||||
|
||||
import "strings"
|
||||
|
||||
func getHostnameFromCity(city string) (hostname string) {
|
||||
host := strings.ToLower(city)
|
||||
host = strings.ReplaceAll(host, ".", "")
|
||||
host = strings.ReplaceAll(host, " ", "")
|
||||
|
||||
specialCases := map[string]string{
|
||||
"washingtondc": "washington",
|
||||
"mexicocity": "mexico",
|
||||
"denizli": "bursa",
|
||||
"sibu": "kualalumpur",
|
||||
"kiev": "kyiv",
|
||||
"stpetersburg": "petersburg",
|
||||
}
|
||||
if specialHost, ok := specialCases[host]; ok {
|
||||
host = specialHost
|
||||
}
|
||||
|
||||
hostname = host + ".wevpn.com"
|
||||
return hostname
|
||||
}
|
||||
33
internal/updater/providers/wevpn/resolve.go
Normal file
33
internal/updater/providers/wevpn/resolve.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package wevpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
)
|
||||
|
||||
func resolveHosts(ctx context.Context, presolver resolver.Parallel,
|
||||
hosts []string, minServers int) (hostToIPs map[string][]net.IP,
|
||||
warnings []string, err error) {
|
||||
const (
|
||||
maxFailRatio = 0.1
|
||||
maxDuration = 20 * time.Second
|
||||
betweenDuration = time.Second
|
||||
maxNoNew = 2
|
||||
maxFails = 2
|
||||
)
|
||||
settings := resolver.ParallelSettings{
|
||||
MaxFailRatio: maxFailRatio,
|
||||
MinFound: minServers,
|
||||
Repeat: resolver.RepeatSettings{
|
||||
MaxDuration: maxDuration,
|
||||
BetweenDuration: betweenDuration,
|
||||
MaxNoNew: maxNoNew,
|
||||
MaxFails: maxFails,
|
||||
SortIPs: true,
|
||||
},
|
||||
}
|
||||
return presolver.Resolve(ctx, hosts, settings)
|
||||
}
|
||||
57
internal/updater/providers/wevpn/resolve_test.go
Normal file
57
internal/updater/providers/wevpn/resolve_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package wevpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver/mock_resolver"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_resolveHosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
ctx := context.Background()
|
||||
presolver := mock_resolver.NewMockParallel(ctrl)
|
||||
hosts := []string{"host1", "host2"}
|
||||
const minServers = 10
|
||||
|
||||
expectedHostToIPs := map[string][]net.IP{
|
||||
"host1": {{1, 2, 3, 4}},
|
||||
"host2": {{2, 3, 4, 5}},
|
||||
}
|
||||
expectedWarnings := []string{"warning1", "warning2"}
|
||||
expectedErr := errors.New("dummy")
|
||||
|
||||
const (
|
||||
maxFailRatio = 0.1
|
||||
maxDuration = 20 * time.Second
|
||||
betweenDuration = time.Second
|
||||
maxNoNew = 2
|
||||
maxFails = 2
|
||||
)
|
||||
expectedSettings := resolver.ParallelSettings{
|
||||
MaxFailRatio: maxFailRatio,
|
||||
MinFound: minServers,
|
||||
Repeat: resolver.RepeatSettings{
|
||||
MaxDuration: maxDuration,
|
||||
BetweenDuration: betweenDuration,
|
||||
MaxNoNew: maxNoNew,
|
||||
MaxFails: maxFails,
|
||||
SortIPs: true,
|
||||
},
|
||||
}
|
||||
presolver.EXPECT().Resolve(ctx, hosts, expectedSettings).
|
||||
Return(expectedHostToIPs, expectedWarnings, expectedErr)
|
||||
|
||||
hostToIPs, warnings, err := resolveHosts(ctx, presolver, hosts, minServers)
|
||||
assert.Equal(t, expectedHostToIPs, hostToIPs)
|
||||
assert.Equal(t, expectedWarnings, warnings)
|
||||
assert.Equal(t, expectedErr, err)
|
||||
}
|
||||
58
internal/updater/providers/wevpn/servers.go
Normal file
58
internal/updater/providers/wevpn/servers.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// package wevpn contains code to obtain the server information
|
||||
// for the WeVPN provider.
|
||||
package wevpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrGetZip = errors.New("cannot get OpenVPN ZIP file")
|
||||
ErrGetAPI = errors.New("cannot fetch server information from API")
|
||||
ErrNotEnoughServers = errors.New("not enough servers found")
|
||||
)
|
||||
|
||||
func GetServers(ctx context.Context, presolver resolver.Parallel, minServers int) (
|
||||
servers []models.WevpnServer, warnings []string, err error) {
|
||||
cities := getAvailableCities()
|
||||
servers = make([]models.WevpnServer, 0, len(cities))
|
||||
hostnames := make([]string, len(cities))
|
||||
hostnameToCity := make(map[string]string, len(cities))
|
||||
|
||||
for i, city := range cities {
|
||||
hostname := getHostnameFromCity(city)
|
||||
hostnames[i] = hostname
|
||||
hostnameToCity[hostname] = city
|
||||
}
|
||||
|
||||
hostnameToIPs, newWarnings, err := resolveHosts(ctx, presolver, hostnames, minServers)
|
||||
warnings = append(warnings, newWarnings...)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
if len(hostnameToIPs) < minServers {
|
||||
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||
ErrNotEnoughServers, len(servers), minServers)
|
||||
}
|
||||
|
||||
for hostname, ips := range hostnameToIPs {
|
||||
city := hostnameToCity[hostname]
|
||||
server := models.WevpnServer{
|
||||
City: city,
|
||||
Hostname: hostname,
|
||||
UDP: true,
|
||||
IPs: ips,
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
sortServers(servers)
|
||||
|
||||
return servers, warnings, nil
|
||||
}
|
||||
16
internal/updater/providers/wevpn/sort.go
Normal file
16
internal/updater/providers/wevpn/sort.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package wevpn
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func sortServers(servers []models.WevpnServer) {
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
if servers[i].City == servers[j].City {
|
||||
return servers[i].Hostname < servers[j].Hostname
|
||||
}
|
||||
return servers[i].City < servers[j].City
|
||||
})
|
||||
}
|
||||
40
internal/updater/providers/wevpn/sort_test.go
Normal file
40
internal/updater/providers/wevpn/sort_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package wevpn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_sortServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
initialServers []models.WevpnServer
|
||||
sortedServers []models.WevpnServer
|
||||
}{
|
||||
"no server": {},
|
||||
"sorted servers": {
|
||||
initialServers: []models.WevpnServer{
|
||||
{City: "A", Hostname: "A"},
|
||||
{City: "A", Hostname: "B"},
|
||||
{City: "A", Hostname: "A"},
|
||||
{City: "B", Hostname: "A"},
|
||||
},
|
||||
sortedServers: []models.WevpnServer{
|
||||
{City: "A", Hostname: "A"},
|
||||
{City: "A", Hostname: "A"},
|
||||
{City: "A", Hostname: "B"},
|
||||
{City: "B", Hostname: "A"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sortServers(testCase.initialServers)
|
||||
assert.Equal(t, testCase.sortedServers, testCase.initialServers)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Wevpn {
|
||||
u.logger.Info("updating WeVPN servers...")
|
||||
if err := u.updateWevpn(ctx); err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return allServers, ctxErr
|
||||
}
|
||||
u.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Windscribe {
|
||||
u.logger.Info("updating Windscribe servers...")
|
||||
if err := u.updateWindscribe(ctx); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user