Feat: WeVPN support (#591)

This commit is contained in:
Quentin McGaw
2021-09-23 07:58:13 -07:00
committed by GitHub
parent 3cd26a9f61
commit d8e008606f
36 changed files with 1533 additions and 8 deletions

View File

@@ -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)

View 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",
}
}

View 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
}

View 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)
}

View 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)
}

View 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
}

View 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
})
}

View 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)
})
}
}

View File

@@ -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 {