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

3
.github/labels.yml vendored
View File

@@ -62,6 +62,9 @@
- name: ":cloud: Vyprvpn" - name: ":cloud: Vyprvpn"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: WeVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Windscribe" - name: ":cloud: Windscribe"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -2,7 +2,7 @@
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN, *Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN, HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN and Windscribe VPN servers ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: Wireguard is now supported for all providers supporting it! **ANNOUNCEMENT**: Wireguard is now supported for all providers supporting it!
@@ -60,7 +60,7 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
## Features ## Features
- Based on Alpine 3.14 for a small Docker image of 31MB - Based on Alpine 3.14 for a small Docker image of 31MB
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers - Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed - Supports OpenVPN for all providers listed
- Supports Wireguard - Supports Wireguard
- For **Mullvad**, **Ivpn** and **Windscribe** - For **Mullvad**, **Ivpn** and **Windscribe**

View File

@@ -27,7 +27,7 @@ func (c *CLI) FormatServers(args []string) error {
var format, output string var format, output string
var cyberghost, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad, var cyberghost, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
nordvpn, pia, privado, privatevpn, protonvpn, purevpn, surfshark, nordvpn, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
torguard, vpnUnlimited, vyprvpn, windscribe bool torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError) flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'") flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to") flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
@@ -47,6 +47,7 @@ func (c *CLI) FormatServers(args []string) error {
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers") flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers") flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers") flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers") flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return err return err
@@ -97,6 +98,8 @@ func (c *CLI) FormatServers(args []string) error {
formatted = currentServers.VPNUnlimited.ToMarkdown() formatted = currentServers.VPNUnlimited.ToMarkdown()
case vyprvpn: case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown() formatted = currentServers.Vyprvpn.ToMarkdown()
case wevpn:
formatted = currentServers.Wevpn.ToMarkdown()
case windscribe: case windscribe:
formatted = currentServers.Windscribe.ToMarkdown() formatted = currentServers.Windscribe.ToMarkdown()
default: default:

View File

@@ -54,6 +54,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger logging.Logger)
flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers") flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers")
flagSet.BoolVar(&options.VPNUnlimited, "vpnunlimited", false, "Update VPN Unlimited servers") flagSet.BoolVar(&options.VPNUnlimited, "vpnunlimited", false, "Update VPN Unlimited servers")
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers") flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
flagSet.BoolVar(&options.Wevpn, "wevpn", false, "Update WeVPN servers")
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers") flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return err return err

View File

@@ -168,6 +168,8 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
settings.EncPreset, err = getPIAEncryptionPreset(r) settings.EncPreset, err = getPIAEncryptionPreset(r)
case constants.VPNUnlimited: case constants.VPNUnlimited:
err = settings.readVPNUnlimited(r) err = settings.readVPNUnlimited(r)
case constants.Wevpn:
err = settings.readWevpn(r)
} }
if err != nil { if err != nil {
return err return err

View File

@@ -83,6 +83,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
err = settings.readVPNUnlimited(r) err = settings.readVPNUnlimited(r)
case constants.Vyprvpn: case constants.Vyprvpn:
err = settings.readVyprvpn(r) err = settings.readVyprvpn(r)
case constants.Wevpn:
err = settings.readWevpn(r)
case constants.Windscribe: case constants.Windscribe:
err = settings.readWindscribe(r) err = settings.readWindscribe(r)
default: default:
@@ -104,7 +106,8 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
constants.Custom, constants.Custom,
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn", "cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn", "privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"} "purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
constants.Wevpn, "windscribe"}
case constants.Wireguard: case constants.Wireguard:
allowedVPNServiceProviders = []string{ allowedVPNServiceProviders = []string{
constants.Custom, constants.Ivpn, constants.Custom, constants.Ivpn,

View File

@@ -322,6 +322,27 @@ func Test_Provider_lines(t *testing.T) {
" |--Protocol: udp", " |--Protocol: udp",
}, },
}, },
"wevpn": {
settings: Provider{
Name: constants.Wevpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Cities: []string{"a", "b"},
Hostnames: []string{"c", "d"},
OpenVPN: OpenVPNSelection{
CustomPort: 1,
},
},
},
lines: []string{
"|--Wevpn settings:",
" |--Cities: a, b",
" |--Hostnames: c, d",
" |--OpenVPN selection:",
" |--Protocol: udp",
" |--Custom port: 1",
},
},
"windscribe": { "windscribe": {
settings: Provider{ settings: Provider{
Name: constants.Windscribe, Name: constants.Windscribe,

View File

@@ -17,9 +17,9 @@ type ServerSelection struct { //nolint:maligned
// Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited // Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
Countries []string `json:"countries"` Countries []string `json:"countries"`
// HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, Windscribe // HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, WeVPN, Windscribe
Cities []string `json:"cities"` Cities []string `json:"cities"`
// Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited // Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
Hostnames []string `json:"hostnames"` Hostnames []string `json:"hostnames"`
Names []string `json:"names"` // Protonvpn Names []string `json:"names"` // Protonvpn
@@ -100,7 +100,7 @@ func (selection ServerSelection) toLines() (lines []string) {
type OpenVPNSelection struct { type OpenVPNSelection struct {
ConfFile string `json:"conf_file"` // Custom configuration file path ConfFile string `json:"conf_file"` // Custom configuration file path
TCP bool `json:"tcp"` // UDP if TCP is false TCP bool `json:"tcp"` // UDP if TCP is false
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, Windscribe CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
} }

View File

@@ -27,6 +27,7 @@ type Updater struct {
Torguard bool `json:"torguard"` Torguard bool `json:"torguard"`
VPNUnlimited bool `json:"vpnunlimited"` VPNUnlimited bool `json:"vpnunlimited"`
Vyprvpn bool `json:"vyprvpn"` Vyprvpn bool `json:"vyprvpn"`
Wevpn bool `json:"wevpn"`
Windscribe bool `json:"windscribe"` Windscribe bool `json:"windscribe"`
// The two below should be used in CLI mode only // The two below should be used in CLI mode only
CLI bool `json:"-"` CLI bool `json:"-"`
@@ -65,6 +66,7 @@ func (settings *Updater) EnableAll() {
settings.Torguard = true settings.Torguard = true
settings.VPNUnlimited = true settings.VPNUnlimited = true
settings.Vyprvpn = true settings.Vyprvpn = true
settings.Wevpn = true
settings.Windscribe = true settings.Windscribe = true
} }

View File

@@ -0,0 +1,57 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readWevpn(r reader) (err error) {
settings.Name = constants.Wevpn
servers := r.servers.GetWevpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WevpnCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.WevpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readWevpn(r)
}
func (settings *OpenVPNSelection) readWevpn(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
validation := openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{53, 1195, 1199, 2018},
allowedUDP: []uint16{80, 1194, 1198},
}
settings.CustomPort, err = readOpenVPNCustomPort(r, validation)
if err != nil {
return err
}
return nil
}
func (settings *OpenVPN) readWevpn(r reader) (err error) {
settings.ClientKey, err = readClientKey(r)
if err != nil {
return err
}
return nil
}

View File

@@ -41,6 +41,8 @@ const (
VPNUnlimited = "vpn unlimited" VPNUnlimited = "vpn unlimited"
// Vyprvpn is a VPN provider. // Vyprvpn is a VPN provider.
Vyprvpn = "vyprvpn" Vyprvpn = "vyprvpn"
// WeVPN is a VPN provider.
Wevpn = "wevpn"
// Windscribe is a VPN provider. // Windscribe is a VPN provider.
Windscribe = "windscribe" Windscribe = "windscribe"
) )

View File

@@ -0,0 +1,26 @@
package constants
import "github.com/qdm12/gluetun/internal/models"
//nolint:lll
const (
WevpnCA = "MIIDQjCCAiqgAwIBAgIUPppqnRZfvGGrT4GjXFE4Q29QzgowDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIQ2hhbmdlTWUwHhcNMTkxMTA1MjMzMzIzWhcNMjkxMTAyMjMzMzIzWjATMREwDwYDVQQDDAhDaGFuZ2VNZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5DFBJlTqhXukJFWlI8TNW9+HEQCZXhyVFvQhJFF2xIGVNx51XzqxiRANjVJZJrA68kV8az0v2Dxj0SFnRWDR6pOjjdp2CyHFcgHyfv+4MrsreAtkue86bB/1ECPWaoIwtaLnwI6SEmFZl98RlI9v4M/8IE4chOnMrM/F22+2OXI//TduvTcbyOMUiiouIP8UG1FB3J5FyuaW6qPZz2G0efDoaOI+E9LSxE87OoFrII7UqdHlWxRb3nUuPU1Ee4rN/d4tFyP4AvPKfsGhVOwyGG21IdRnbXIuDi0xytkCGOZ4j2bq5zqudnp4Izt6yJgdzZpQQWK3kSHB3qTT/Yzl8CAwEAAaOBjTCBijAdBgNVHQ4EFgQUXYkoo4WbkkvbgLVdGob9RScRf3AwTgYDVR0jBEcwRYAUXYkoo4WbkkvbgLVdGob9RScRf3ChF6QVMBMxETAPBgNVBAMMCENoYW5nZU1lghQ+mmqdFl+8YatPgaNcUThDb1DOCjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAOr1XmyWBRYfTQPNvZZ+DjCfiRYzLOi2AGefZt/jETqPDF8deVbyL1fLhXZzuX+5Etlsil3PflJjpzc/FSeZRuYRaShtwF3j6I08Eww9rBkaCnsukMUcLtMOvhdAU8dUakcRA2wkQ7Z+TWdMBv5+/6MnX10An1fIz7bAy3btMEOPTEFLo8Bst1SxJtUMaqhUteSOJ1VorpK3CWfOFaXxbJAb4E0+3zt6Vsc8mY5tt6wAi8IqiN4WD79ZdvKxENK4FMkR1kNpBY97mvdf82rzpwiBuJgN5ywmH78Ghj+9T8nI6/UIqJ1y22IRYGv6dMif8fHo5WWhCv3qmCqqY8vwuxw=="
WevpnCertificate = "MIIDTDCCAjSgAwIBAgIRAKxt8SMIXezjmHm2KDCAQdIwDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIQ2hhbmdlTWUwHhcNMTkxMTA1MjMzMzI0WhcNMjkxMTAyMjMzMzI0WjAOMQwwCgYDVQQDDAN0Y3AwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvEwY2erLhMm3Mpsnybm3G6zvGyeblUAaehQVEUs+KM2/5np0Ovx0y8Iz9pIC9ITaWM0B3dM6uBsNEtylZIe4Dd9aFujunSeCFsLRf8i9AbrUombpQ6P4jzYFBxwcEw//UShwa4HZI6JuSYikdpx/dyXdBH2skahwDVc8VUFdBLLSglfKGbuzP9GsdSwQCeBRWgA3dvIzIkQkBwfnt9WQKUfRAe8e5NybaAn8Yuu9sjLkQe6eyV7toxkZTcEXdABG2vtdTEzlAsQilZzIxg3jcdeEgMgRKngng+YNP0rR5nofZ1iDlp+vBj0nuqTTJLHMrRWPIc7bdYFD/f2J49WORAgMBAAGjgZ8wgZwwCQYDVR0TBAIwADAdBgNVHQ4EFgQUmSAFmCo1FAKVq8RQF7jMxMxcMtUwTgYDVR0jBEcwRYAUXYkoo4WbkkvbgLVdGob9RScRf3ChF6QVMBMxETAPBgNVBAMMCENoYW5nZU1lghQ+mmqdFl+8YatPgaNcUThDb1DOCjATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBADPqdEgL+0kou8P974QEaNg1XOAXpwP0NNqbkZ/Oj9+Lp96YAhAHOAJig+RWbBktK8zu8oUUGR1qLXAWCmirlXErVuBRnadTEh3A7SOuY02BcsYAtpQ2EU9j5K/LV7nTfagkVdWy7x/av361UD4t9fv1j4YYTh4XLRp7KVXs6AGZ7T1hqPYFMUIoPpFhPzFxH4euJjfazr4SkTR6k6Vhw3pyFd6HP65vcqpzHGxFytSa8HtltBk2DpzIf8yV9TEy+gOXFaaGss0YKQ5OU1ieqZRuLVEGiu17lByYiQGyemIETJbdkyiSg93dDJRxjaTk7c8CEdpipt07ndSIPldMtXA="
WevpnOpenvpnStaticKeyV1 = "7be66c0df0b8855e076d9e37b19f9ff3c1735ed537dee6dc786e51bdb8502f878077eeba0420a25e2b04814d22bbdcc0191a4fc396fdba1af6eb090a9d8664f18e70012ee98a2e32c28620a771d13cf3a619c417480c2c312562fffaebfd7ba73f57a28edde6c287365e6ce28291a29728da211cb53e01aa46b92f5f276c61fb46bd810b41219022c8f3d9e699fe9ade6bfcbb937fbbf6f49d741740e71c7c008a9a13c2432608038c6310b4f33588d8d234b3dffcf0823395267d73140d0e9a40e323ca92866c37073bfb072ab9de518bb9f2c65df7e219c2f114afbcf7c6e3c401cb08c3ed2901725b0601d2b5de89245719dd32506d52f149d14156215c1e"
)
func WevpnCityChoices(servers []models.WevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func WevpnHostnameChoices(servers []models.WevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -220,6 +220,18 @@ func (a *AllServers) GetVyprvpn() (servers []VyprvpnServer) {
return servers return servers
} }
func (a *AllServers) GetWevpn() (servers []WevpnServer) {
if a.Windscribe.Servers == nil {
return nil
}
servers = make([]WevpnServer, len(a.Wevpn.Servers))
for i, serverToCopy := range a.Wevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetWindscribe() (servers []WindscribeServer) { func (a *AllServers) GetWindscribe() (servers []WindscribeServer) {
if a.Windscribe.Servers == nil { if a.Windscribe.Servers == nil {
return nil return nil

View File

@@ -239,6 +239,19 @@ func (s *VyprvpnServer) ToMarkdown() (markdown string) {
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP)) boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
} }
func (s *WevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *WevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.City, s.Hostname, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *WindscribeServers) ToMarkdown() (markdown string) { func (s *WindscribeServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "City", "Hostname", "VPN") markdown = markdownTableHeading("Region", "City", "Hostname", "VPN")
for _, server := range s.Servers { for _, server := range s.Servers {

View File

@@ -157,6 +157,14 @@ type VyprvpnServer struct {
IPs []net.IP `json:"ips"` IPs []net.IP `json:"ips"`
} }
type WevpnServer struct {
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type WindscribeServer struct { type WindscribeServer struct {
VPN string `json:"vpn"` VPN string `json:"vpn"`
Region string `json:"region"` Region string `json:"region"`

View File

@@ -18,6 +18,7 @@ type AllServers struct {
Torguard TorguardServers `json:"torguard"` Torguard TorguardServers `json:"torguard"`
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"` VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
Vyprvpn VyprvpnServers `json:"vyprvpn"` Vyprvpn VyprvpnServers `json:"vyprvpn"`
Wevpn WevpnServers `json:"wevpn"`
Windscribe WindscribeServers `json:"windscribe"` Windscribe WindscribeServers `json:"windscribe"`
} }
@@ -38,6 +39,7 @@ func (a *AllServers) Count() int {
len(a.Torguard.Servers) + len(a.Torguard.Servers) +
len(a.VPNUnlimited.Servers) + len(a.VPNUnlimited.Servers) +
len(a.Vyprvpn.Servers) + len(a.Vyprvpn.Servers) +
len(a.Wevpn.Servers) +
len(a.Windscribe.Servers) len(a.Windscribe.Servers)
} }
@@ -121,6 +123,11 @@ type VyprvpnServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []VyprvpnServer `json:"servers"` Servers []VyprvpnServer `json:"servers"`
} }
type WevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []WevpnServer `json:"servers"`
}
type WindscribeServers struct { type WindscribeServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -28,6 +28,7 @@ import (
"github.com/qdm12/gluetun/internal/provider/torguard" "github.com/qdm12/gluetun/internal/provider/torguard"
"github.com/qdm12/gluetun/internal/provider/vpnunlimited" "github.com/qdm12/gluetun/internal/provider/vpnunlimited"
"github.com/qdm12/gluetun/internal/provider/vyprvpn" "github.com/qdm12/gluetun/internal/provider/vyprvpn"
"github.com/qdm12/gluetun/internal/provider/wevpn"
"github.com/qdm12/gluetun/internal/provider/windscribe" "github.com/qdm12/gluetun/internal/provider/windscribe"
"github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/logging"
) )
@@ -85,6 +86,8 @@ func New(provider string, allServers models.AllServers, timeNow func() time.Time
return vpnunlimited.New(allServers.VPNUnlimited.Servers, randSource) return vpnunlimited.New(allServers.VPNUnlimited.Servers, randSource)
case constants.Vyprvpn: case constants.Vyprvpn:
return vyprvpn.New(allServers.Vyprvpn.Servers, randSource) return vyprvpn.New(allServers.Vyprvpn.Servers, randSource)
case constants.Wevpn:
return wevpn.New(allServers.Wevpn.Servers, randSource)
case constants.Windscribe: case constants.Windscribe:
return windscribe.New(allServers.Windscribe.Servers, randSource) return windscribe.New(allServers.Windscribe.Servers, randSource)
default: default:

View File

@@ -0,0 +1,43 @@
package wevpn
import (
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (w *Wevpn) GetConnection(selection configuration.ServerSelection) (
connection models.Connection, err error) {
port := getPort(selection)
protocol := utils.GetProtocol(selection)
servers, err := w.filterServers(selection)
if err != nil {
return connection, err
}
var connections []models.Connection
for _, server := range servers {
for _, IP := range server.IPs {
connection := models.Connection{
Type: selection.VPN,
IP: IP,
Port: port,
Protocol: protocol,
}
connections = append(connections, connection)
}
}
return utils.PickConnection(connections, selection, w.randSource)
}
func getPort(selection configuration.ServerSelection) (port uint16) {
const (
defaultOpenVPNTCP = 1195
defaultOpenVPNUDP = 1194
defaultWireguard = 0 // Wireguard not supported
)
return utils.GetPort(selection, defaultOpenVPNTCP,
defaultOpenVPNUDP, defaultWireguard)
}

View File

@@ -0,0 +1,96 @@
package wevpn
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Wevpn_GetConnection(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
servers []models.WevpnServer
selection configuration.ServerSelection
connection models.Connection
err error
}{
"no server available": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
},
err: errors.New("no server found: for VPN openvpn; protocol udp"),
},
"no filter": {
servers: []models.WevpnServer{
{UDP: true, IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{UDP: true, IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{UDP: true, IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
IP: net.IPv4(1, 1, 1, 1),
Port: 1194,
Protocol: constants.UDP,
},
},
"target IP": {
selection: configuration.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
},
servers: []models.WevpnServer{
{UDP: true, IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{UDP: true, IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{UDP: true, IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
},
},
"with filter": {
selection: configuration.ServerSelection{
Hostnames: []string{"b"},
},
servers: []models.WevpnServer{
{UDP: true, Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{UDP: true, Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{UDP: true, Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
connection, err := m.GetConnection(testCase.selection)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.connection, connection)
})
}
}

View File

@@ -0,0 +1,27 @@
package wevpn
import (
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (w *Wevpn) filterServers(selection configuration.ServerSelection) (
servers []models.WevpnServer, err error) {
for _, server := range w.servers {
switch {
case
utils.FilterByProtocol(selection, server.TCP, server.UDP),
utils.FilterByPossibilities(server.City, selection.Cities),
utils.FilterByPossibilities(server.Hostname, selection.Hostnames):
default:
servers = append(servers, server)
}
}
if len(servers) == 0 {
return nil, utils.NoServerFoundError(selection)
}
return servers, nil
}

View File

@@ -0,0 +1,104 @@
package wevpn
import (
"errors"
"math/rand"
"testing"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Wevpn_filterServers(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
servers []models.WevpnServer
selection configuration.ServerSelection
filtered []models.WevpnServer
err error
}{
"no server available": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
},
err: errors.New("no server found: for VPN openvpn; protocol udp"),
},
"no filter": {
servers: []models.WevpnServer{
{Hostname: "a", UDP: true},
{Hostname: "b", UDP: true},
{Hostname: "c", UDP: true},
},
filtered: []models.WevpnServer{
{Hostname: "a", UDP: true},
{Hostname: "b", UDP: true},
{Hostname: "c", UDP: true},
},
},
"filter by protocol": {
selection: configuration.ServerSelection{
OpenVPN: configuration.OpenVPNSelection{TCP: true},
},
servers: []models.WevpnServer{
{Hostname: "a", UDP: true},
{Hostname: "b", TCP: true},
{Hostname: "c", UDP: true},
},
filtered: []models.WevpnServer{
{Hostname: "b", TCP: true},
},
},
"filter by city": {
selection: configuration.ServerSelection{
Cities: []string{"b"},
},
servers: []models.WevpnServer{
{City: "a", UDP: true},
{City: "b", UDP: true},
{City: "c", UDP: true},
},
filtered: []models.WevpnServer{
{City: "b", UDP: true},
},
},
"filter by hostname": {
selection: configuration.ServerSelection{
Hostnames: []string{"b"},
},
servers: []models.WevpnServer{
{Hostname: "a", UDP: true},
{Hostname: "b", UDP: true},
{Hostname: "c", UDP: true},
},
filtered: []models.WevpnServer{
{Hostname: "b", UDP: true},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
randSource := rand.NewSource(0)
w := New(testCase.servers, randSource)
servers, err := w.filterServers(testCase.selection)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.filtered, servers)
})
}
}

View File

@@ -0,0 +1,84 @@
package wevpn
import (
"strconv"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (w *Wevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
settings.Cipher = constants.AES256gcm
}
if settings.Auth == "" {
settings.Auth = constants.SHA512
}
lines = []string{
"client",
"nobind",
"tls-exit",
"dev " + settings.Interface,
"verb " + strconv.Itoa(settings.Verbosity),
// Wevpn specific
"ping 30",
"remote-cert-tls server",
"redirect-gateway def1 bypass-dhcp",
"reneg-sec 0",
"auth-user-pass " + constants.OpenVPNAuthConf,
"auth " + settings.Auth,
// Added constant values
"auth-nocache",
"mute-replay-warnings",
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
"auth-retry nointeract",
"suppress-timestamps",
// Modified variables
connection.OpenVPNProtoLine(),
connection.OpenVPNRemoteLine(),
}
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
if !settings.Root {
lines = append(lines, "user "+settings.ProcUser)
lines = append(lines, "persist-tun")
lines = append(lines, "persist-key")
}
if settings.MSSFix > 0 {
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
}
if settings.IPv6 {
lines = append(lines, "tun-ipv6")
} else {
lines = append(lines, `pull-filter ignore "route-ipv6"`)
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
}
lines = append(lines, utils.WrapOpenvpnKey(
settings.ClientKey)...)
lines = append(lines, utils.WrapOpenvpnCA(
constants.WevpnCA)...)
lines = append(lines, utils.WrapOpenvpnCert(
constants.WevpnCertificate)...)
lines = append(lines, utils.WrapOpenvpnTLSCrypt(
constants.WevpnOpenvpnStaticKeyV1)...)
lines = append(lines, "")
return lines, nil
}

View File

@@ -0,0 +1,23 @@
package wevpn
import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Wevpn struct {
servers []models.WevpnServer
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.WevpnServer, randSource rand.Source) *Wevpn {
return &Wevpn{
servers: servers,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(constants.Wevpn),
}
}

View File

@@ -128,6 +128,11 @@ func Test_versions(t *testing.T) {
version: allServers.Vyprvpn.Version, version: allServers.Vyprvpn.Version,
digest: "58de06d8", digest: "58de06d8",
}, },
"Wevpn": {
model: models.WevpnServer{},
version: allServers.Wevpn.Version,
digest: "f4daa186",
},
"Windscribe": { "Windscribe": {
model: models.WindscribeServer{}, model: models.WindscribeServer{},
version: allServers.Windscribe.Version, version: allServers.Windscribe.Version,

View File

@@ -49,6 +49,7 @@ func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard), Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited), VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited),
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn), Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn),
Wevpn: s.mergeWevpn(hardcoded.Wevpn, persisted.Wevpn),
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe), Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe),
} }
} }
@@ -279,6 +280,21 @@ func (s *Storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) model
return persisted return persisted
} }
func (s *Storage) mergeWevpn(hardcoded, persisted models.WevpnServers) models.WevpnServers {
if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded
}
versionDiff := int(hardcoded.Version) - int(persisted.Version)
if versionDiff > 0 {
s.logVersionDiff("WeVPN", versionDiff)
return hardcoded
}
s.logTimeDiff("WeVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted
}
func (s *Storage) mergeWindscribe(hardcoded, persisted models.WindscribeServers) models.WindscribeServers { func (s *Storage) mergeWindscribe(hardcoded, persisted models.WindscribeServers) models.WindscribeServers {
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded return hardcoded

View File

@@ -107914,6 +107914,633 @@
} }
] ]
}, },
"wevpn": {
"version": 1,
"timestamp": 1632407719,
"servers": [
{
"city": "Amsterdam",
"hostname": "amsterdam.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"89.238.177.234"
]
},
{
"city": "Athens",
"hostname": "athens.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"185.51.134.210"
]
},
{
"city": "Atlanta",
"hostname": "atlanta.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"104.223.91.146"
]
},
{
"city": "Auckland",
"hostname": "auckland.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"103.108.94.226"
]
},
{
"city": "Belgrade",
"hostname": "belgrade.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"37.120.193.170"
]
},
{
"city": "Brussels",
"hostname": "brussels.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"77.243.191.178"
]
},
{
"city": "Bucharest",
"hostname": "bucharest.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"86.105.25.162"
]
},
{
"city": "Budapest",
"hostname": "budapest.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"185.128.26.130"
]
},
{
"city": "Buenos Aires",
"hostname": "buenosaires.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"131.255.4.140"
]
},
{
"city": "Cairo",
"hostname": "cairo.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"188.214.122.138"
]
},
{
"city": "Chennai",
"hostname": "chennai.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"169.38.107.52"
]
},
{
"city": "Chicago",
"hostname": "chicago.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"69.39.239.57"
]
},
{
"city": "Copenhagen",
"hostname": "copenhagen.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"37.120.194.82"
]
},
{
"city": "Dallas",
"hostname": "dallas.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"194.110.112.74"
]
},
{
"city": "Denizli",
"hostname": "bursa.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"95.173.161.240"
]
},
{
"city": "Denver",
"hostname": "denver.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"70.39.71.2"
]
},
{
"city": "Dubai",
"hostname": "dubai.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.193.42"
]
},
{
"city": "Dublin",
"hostname": "dublin.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.222.138"
]
},
{
"city": "Frankfurt",
"hostname": "frankfurt.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"45.141.152.178"
]
},
{
"city": "Hanoi",
"hostname": "hanoi.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"188.214.152.194"
]
},
{
"city": "Helsinki",
"hostname": "helsinki.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"185.212.149.152"
]
},
{
"city": "Hong Kong",
"hostname": "hongkong.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"84.17.37.55"
]
},
{
"city": "Jakarta",
"hostname": "jakarta.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"45.133.181.58"
]
},
{
"city": "Johannesburg",
"hostname": "johannesburg.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"102.165.20.38"
]
},
{
"city": "Kiev",
"hostname": "kyiv.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"37.19.218.197"
]
},
{
"city": "Lagos",
"hostname": "lagos.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"102.129.144.142"
]
},
{
"city": "Lisbon",
"hostname": "lisbon.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"185.90.57.152"
]
},
{
"city": "London",
"hostname": "london.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"45.141.154.2"
]
},
{
"city": "London-PF",
"hostname": "london-pf.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"77.243.177.78"
]
},
{
"city": "Los Angeles",
"hostname": "losangeles.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"45.152.182.82"
]
},
{
"city": "Los Angeles-PF",
"hostname": "losangeles-pf.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"66.55.92.75"
]
},
{
"city": "Luxembourg",
"hostname": "luxembourg.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"5.253.204.194"
]
},
{
"city": "Madrid",
"hostname": "madrid.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"45.152.183.242"
]
},
{
"city": "Manchester",
"hostname": "manchester.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"86.106.136.98"
]
},
{
"city": "Manila",
"hostname": "manila.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"188.214.125.106"
]
},
{
"city": "Melbourne",
"hostname": "melbourne.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"116.206.230.130"
]
},
{
"city": "Mexico City",
"hostname": "mexico.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"169.57.35.97"
]
},
{
"city": "Miami",
"hostname": "miami.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"96.47.224.2"
]
},
{
"city": "Milan",
"hostname": "milan.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.197.138"
]
},
{
"city": "Montreal",
"hostname": "montreal.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.200.242"
]
},
{
"city": "Moscow",
"hostname": "moscow.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"80.93.181.194"
]
},
{
"city": "New Jersey",
"hostname": "newjersey.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"23.226.131.146"
]
},
{
"city": "New York",
"hostname": "newyork.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.206.106"
]
},
{
"city": "New York-PF",
"hostname": "newyork-pf.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"188.241.179.2"
]
},
{
"city": "Oslo",
"hostname": "oslo.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"84.247.50.10"
]
},
{
"city": "Oulu",
"hostname": "oulu.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"188.126.89.174"
]
},
{
"city": "Paris",
"hostname": "paris.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"37.120.136.234"
]
},
{
"city": "Phoenix",
"hostname": "phoenix.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"170.130.15.34"
]
},
{
"city": "Prague",
"hostname": "prague.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.199.138"
]
},
{
"city": "Salt Lake City",
"hostname": "saltlakecity.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"107.182.234.233"
]
},
{
"city": "San Jose",
"hostname": "sanjose.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"169.62.109.140"
]
},
{
"city": "Sao Paulo",
"hostname": "saopaulo.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"177.54.152.89"
]
},
{
"city": "Seattle",
"hostname": "seattle.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"104.140.21.178"
]
},
{
"city": "Seoul",
"hostname": "seoul.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"169.56.83.206"
]
},
{
"city": "Sibu",
"hostname": "kualalumpur.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"223.25.246.4"
]
},
{
"city": "Singapore",
"hostname": "singapore.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"84.17.38.144"
]
},
{
"city": "Sofia",
"hostname": "sofia.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.221.42"
]
},
{
"city": "St Petersburg",
"hostname": "petersburg.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"82.202.220.242"
]
},
{
"city": "Stockholm",
"hostname": "stockholm.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"45.83.91.66"
]
},
{
"city": "Sydney",
"hostname": "sydney.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"46.102.153.106"
]
},
{
"city": "Taipei",
"hostname": "taipei.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"188.214.106.98"
]
},
{
"city": "Tel Aviv",
"hostname": "telaviv.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"193.43.72.242"
]
},
{
"city": "Tokyo",
"hostname": "tokyo.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"84.17.34.8"
]
},
{
"city": "Toronto",
"hostname": "toronto.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"184.75.208.234"
]
},
{
"city": "Vancouver",
"hostname": "vancouver.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"71.19.249.109"
]
},
{
"city": "Vienna",
"hostname": "vienna.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"37.120.212.162"
]
},
{
"city": "Warsaw",
"hostname": "warsaw.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"217.138.209.122"
]
},
{
"city": "Washington DC",
"hostname": "washington.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"70.32.0.208"
]
},
{
"city": "Zurich",
"hostname": "zurich.wevpn.com",
"tcp": false,
"udp": true,
"ips": [
"37.120.137.82"
]
}
]
},
"windscribe": { "windscribe": {
"version": 1, "version": 1,
"timestamp": 1629420754, "timestamp": 1629420754,
@@ -115123,4 +115750,4 @@
} }
] ]
} }
} }

View File

@@ -30,6 +30,7 @@ func countServers(allServers models.AllServers) int {
len(allServers.Torguard.Servers) + len(allServers.Torguard.Servers) +
len(allServers.VPNUnlimited.Servers) + len(allServers.VPNUnlimited.Servers) +
len(allServers.Vyprvpn.Servers) + len(allServers.Vyprvpn.Servers) +
len(allServers.Wevpn.Servers) +
len(allServers.Windscribe.Servers) len(allServers.Windscribe.Servers)
} }

View File

@@ -22,6 +22,7 @@ import (
"github.com/qdm12/gluetun/internal/updater/providers/torguard" "github.com/qdm12/gluetun/internal/updater/providers/torguard"
"github.com/qdm12/gluetun/internal/updater/providers/vpnunlimited" "github.com/qdm12/gluetun/internal/updater/providers/vpnunlimited"
"github.com/qdm12/gluetun/internal/updater/providers/vyprvpn" "github.com/qdm12/gluetun/internal/updater/providers/vyprvpn"
"github.com/qdm12/gluetun/internal/updater/providers/wevpn"
"github.com/qdm12/gluetun/internal/updater/providers/windscribe" "github.com/qdm12/gluetun/internal/updater/providers/windscribe"
) )
@@ -357,6 +358,27 @@ func (u *updater) updateVyprvpn(ctx context.Context) (err error) {
return nil 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) { func (u *updater) updateWindscribe(ctx context.Context) (err error) {
minServers := getMinServers(len(u.servers.Windscribe.Servers)) minServers := getMinServers(len(u.servers.Windscribe.Servers))
servers, err := windscribe.GetServers(ctx, u.client, minServers) 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 { if u.options.Windscribe {
u.logger.Info("updating Windscribe servers...") u.logger.Info("updating Windscribe servers...")
if err := u.updateWindscribe(ctx); err != nil { if err := u.updateWindscribe(ctx); err != nil {