Feat: ExpressVPN support (#623)
This commit is contained in:
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@@ -27,6 +27,9 @@
|
|||||||
- name: ":cloud: IVPN"
|
- name: ":cloud: IVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: ExpressVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: FastestVPN"
|
- name: ":cloud: FastestVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
|
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, 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, WeVPN 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*
|
||||||
@@ -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**, **WeVPN**, **Windscribe** servers
|
- Supports: **Cyberghost**, **ExpressVPN**, **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**
|
||||||
|
|||||||
@@ -25,13 +25,14 @@ var (
|
|||||||
|
|
||||||
func (c *CLI) FormatServers(args []string) error {
|
func (c *CLI) FormatServers(args []string) error {
|
||||||
var format, output string
|
var format, output string
|
||||||
var cyberghost, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||||
nordvpn, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
nordvpn, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||||
torguard, vpnUnlimited, vyprvpn, wevpn, 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")
|
||||||
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
||||||
|
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
||||||
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
||||||
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
||||||
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
||||||
@@ -68,6 +69,8 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
switch {
|
switch {
|
||||||
case cyberghost:
|
case cyberghost:
|
||||||
formatted = currentServers.Cyberghost.ToMarkdown()
|
formatted = currentServers.Cyberghost.ToMarkdown()
|
||||||
|
case expressvpn:
|
||||||
|
formatted = currentServers.Expressvpn.ToMarkdown()
|
||||||
case fastestvpn:
|
case fastestvpn:
|
||||||
formatted = currentServers.Fastestvpn.ToMarkdown()
|
formatted = currentServers.Fastestvpn.ToMarkdown()
|
||||||
case hideMyAss:
|
case hideMyAss:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||||
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
||||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
||||||
|
flagSet.BoolVar(&options.Expressvpn, "expressvpn", false, "Update ExpressVPN servers")
|
||||||
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
|
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
|
||||||
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
|
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
|
||||||
flagSet.BoolVar(&options.Ipvanish, "ipvanish", false, "Update IpVanish servers")
|
flagSet.BoolVar(&options.Ipvanish, "ipvanish", false, "Update IpVanish servers")
|
||||||
|
|||||||
40
internal/configuration/expressvpn.go
Normal file
40
internal/configuration/expressvpn.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (settings *Provider) readExpressvpn(r reader) (err error) {
|
||||||
|
settings.Name = constants.Expressvpn
|
||||||
|
servers := r.servers.GetExpressvpn()
|
||||||
|
|
||||||
|
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
||||||
|
constants.ExpressvpnHostnameChoices(servers))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ExpressvpnCountriesChoices(servers))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.ExpressvpnCityChoices(servers))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("environment variable CITY: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.OpenVPN.TCP, err = readOpenVPNProtocol(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -53,6 +53,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
|
|||||||
err = settings.readCustom(r, vpnType)
|
err = settings.readCustom(r, vpnType)
|
||||||
case constants.Cyberghost:
|
case constants.Cyberghost:
|
||||||
err = settings.readCyberghost(r)
|
err = settings.readCyberghost(r)
|
||||||
|
case constants.Expressvpn:
|
||||||
|
err = settings.readExpressvpn(r)
|
||||||
case constants.Fastestvpn:
|
case constants.Fastestvpn:
|
||||||
err = settings.readFastestvpn(r)
|
err = settings.readFastestvpn(r)
|
||||||
case constants.HideMyAss:
|
case constants.HideMyAss:
|
||||||
@@ -104,7 +106,8 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
|
|||||||
case constants.OpenVPN:
|
case constants.OpenVPN:
|
||||||
allowedVPNServiceProviders = []string{
|
allowedVPNServiceProviders = []string{
|
||||||
constants.Custom,
|
constants.Custom,
|
||||||
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
|
"cyberghost", constants.Expressvpn, "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",
|
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
|
||||||
constants.Wevpn, "windscribe"}
|
constants.Wevpn, "windscribe"}
|
||||||
|
|||||||
@@ -35,6 +35,25 @@ func Test_Provider_lines(t *testing.T) {
|
|||||||
" |--Protocol: udp",
|
" |--Protocol: udp",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"expressvpn": {
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Expressvpn,
|
||||||
|
ServerSelection: ServerSelection{
|
||||||
|
VPN: constants.OpenVPN,
|
||||||
|
Hostnames: []string{"a", "b"},
|
||||||
|
Countries: []string{"c", "d"},
|
||||||
|
Cities: []string{"e", "f"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"|--Expressvpn settings:",
|
||||||
|
" |--Countries: c, d",
|
||||||
|
" |--Cities: e, f",
|
||||||
|
" |--Hostnames: a, b",
|
||||||
|
" |--OpenVPN selection:",
|
||||||
|
" |--Protocol: udp",
|
||||||
|
},
|
||||||
|
},
|
||||||
"fastestvpn": {
|
"fastestvpn": {
|
||||||
settings: Provider{
|
settings: Provider{
|
||||||
Name: constants.Fastestvpn,
|
Name: constants.Fastestvpn,
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
|
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||||
Regions []string `json:"regions"`
|
Regions []string `json:"regions"`
|
||||||
|
|
||||||
// Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
// Expressvpn, 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, WeVPN, Windscribe
|
// Expressvpn, 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, WeVPN
|
// Expressvpn, 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
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type Updater struct {
|
|||||||
Period time.Duration `json:"period"`
|
Period time.Duration `json:"period"`
|
||||||
DNSAddress string `json:"dns_address"`
|
DNSAddress string `json:"dns_address"`
|
||||||
Cyberghost bool `json:"cyberghost"`
|
Cyberghost bool `json:"cyberghost"`
|
||||||
|
Expressvpn bool `json:"expressvpn"`
|
||||||
Fastestvpn bool `json:"fastestvpn"`
|
Fastestvpn bool `json:"fastestvpn"`
|
||||||
HideMyAss bool `json:"hidemyass"`
|
HideMyAss bool `json:"hidemyass"`
|
||||||
Ipvanish bool `json:"ipvanish"`
|
Ipvanish bool `json:"ipvanish"`
|
||||||
|
|||||||
37
internal/constants/expressvpn.go
Normal file
37
internal/constants/expressvpn.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
ExpressvpnCert = "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA=="
|
||||||
|
ExpressvpnRSAKey = "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw=="
|
||||||
|
ExpressvpnTLSAuthOpenvpnStaticKeyV1 = "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6"
|
||||||
|
ExpressvpnCA = "MIIF+DCCA+CgAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBhDELMAkGA1UEBhMCVkcxDDAKBgNVBAgMA0JWSTETMBEGA1UECgwKRXhwcmVzc1ZQTjETMBEGA1UECwwKRXhwcmVzc1ZQTjEWMBQGA1UEAwwNRXhwcmVzc1ZQTiBDQTElMCMGCSqGSIb3DQEJARYWc3VwcG9ydEBleHByZXNzdnBuLmNvbTAeFw0xNTEwMjEwMDAwMDBaFw0yNjA0MDEyMTEyMDBaMIGEMQswCQYDVQQGEwJWRzEMMAoGA1UECAwDQlZJMRMwEQYDVQQKDApFeHByZXNzVlBOMRMwEQYDVQQLDApFeHByZXNzVlBOMRYwFAYDVQQDDA1FeHByZXNzVlBOIENBMSUwIwYJKoZIhvcNAQkBFhZzdXBwb3J0QGV4cHJlc3N2cG4uY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxzXvHZ25OsESKRMQFINHJNqE9kVRLWJS50oVB2jxobudPhCsWvJSApvar8CB2RrqkVMhXu2HT3FBtDL91INg070qAyjjRpzEbDPWqQ1+G0tk0sjiJt2mXPJK2IlNFnhe6rTs09Pkpcp8qRhfZay/dIlmagohQAr4JvYL1Ajg9A3sLb8JkY03H6GhOF8EKYTqhrEppCcg4sQKQhNSytRoQAm8Ta+tnTYIedwWpqjUXP9YXFOvljPaixfYug24eAkpTjeuWTcELSyfnuiBeK+z9+5OYunhqFt2QZMq33kLFZGMN2gHRCzngxxphurypsPRo7jiFgQI1yLt8uZsEZ+otGEK91jjKfOC+g9TBy2RUtxk1neWcQ6syXDuc3rBNrGA8iM0ZoEqQ1BC8xWr3NYlSjqN+1mgpTAX3/Dxze4GzHd7AmYaYJV8xnKBVNphlMlg1giCAu5QXjMxPbfCgZiEFq/uq0SOKQJeT3AI/uVPSvwCMWByjyMbDpKKAK8Hy3UT5m4bCNu8J7bxj+vdnq0A2HPwtF0FwBl/TIM3zNsyFrZZ0j6jLRT50mFsgDBKcD4L/J5rjdCsKPu5rodhxe38rCx2GknP1Zkov4yoVCcR48+CQwg3oBkq0/EflvWUvcYApzs9SomUM/g+8Q/V0WOfJmFWuxN9YntZlnzHRSRjrvMCAwEAAaNzMHEwHQYDVR0OBBYEFIzmQGj8xS+0LLklwqHD45VVOZRJMB8GA1UdIwQYMBaAFIzmQGj8xS+0LLklwqHD45VVOZRJMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIBFjANBgkqhkiG9w0BAQ0FAAOCAgEAbHfuMKtojm1NgX7qSU2Rm2B5L8G0FuFP0L40dj8O5WHt45j2z8coMK90vrUnQEZNQmRzot7v3XjVzVlxBWYSsCEApTsSDNi/4BNFP8H/BUUtJuy2GFTO4wDVJnqNkZOHBmyVD75s1Y+W8a+zB4jkMeDEhOHZdwQ0l1fJDDgXal5f1UT5F5WH6/RwHmWTwX4GxuCiIVtx70CjkXqhM8yZtTp1UtHLRNYcNSIes0vrAPHPgoA5z9B8UvsOjuP+mfcjzi0LGGrY+2pJu0BKO2dRnarIZZABETIisI3FokoTszx5jpRPyxyUTuRDKWHrvi0PPtOmC8nFahfugWFUi6uBsqCaSeuex+ahnTPCq0b1l0Ozpg0YeE8CW1TL9Y92b01up2c+PP6wZOIm3JyTH+L5smDFbh80V42dKyGNdPXMg5IcJhj3YfAy4k8h/qbWY57KFcIzKx40bFsoI7PeydbGtT/dIoFLSZRLW5bleXNgG9mXZp270UeEC6CpATCS6uVl8LVT1I02uulHUpFaRmTEOrmMxsXGt6UAwYTY55K/B8uuID341xKbeC0kzhuN2gsL5UJaocBHyWK/AqwbeBttdhOCLwoaj7+nSViPxICObKrg3qavGNCvtwy/fEegK9X/wlp2e2CFlIhFbadeXOBr9Fn8ypYPP17mTqe98OJYM04="
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []st
|
|||||||
for i := range servers {
|
for i := range servers {
|
||||||
choices[i] = servers[i].Country
|
choices[i] = servers[i].Country
|
||||||
}
|
}
|
||||||
return choices
|
return makeUnique(choices)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
|
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
|
||||||
@@ -23,5 +23,5 @@ func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []str
|
|||||||
for i := range servers {
|
for i := range servers {
|
||||||
choices[i] = servers[i].Hostname
|
choices[i] = servers[i].Hostname
|
||||||
}
|
}
|
||||||
return choices
|
return makeUnique(choices)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const (
|
|||||||
Custom = "custom"
|
Custom = "custom"
|
||||||
// Cyberghost is a VPN provider.
|
// Cyberghost is a VPN provider.
|
||||||
Cyberghost = "cyberghost"
|
Cyberghost = "cyberghost"
|
||||||
|
// Expressvpn is a VPN provider.
|
||||||
|
Expressvpn = "expressvpn"
|
||||||
// Fastestvpn is a VPN provider.
|
// Fastestvpn is a VPN provider.
|
||||||
Fastestvpn = "fastestvpn"
|
Fastestvpn = "fastestvpn"
|
||||||
// HideMyAss is a VPN provider.
|
// HideMyAss is a VPN provider.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
func (a AllServers) GetCopy() (servers AllServers) {
|
func (a AllServers) GetCopy() (servers AllServers) {
|
||||||
servers = a // copy versions and timestamps
|
servers = a // copy versions and timestamps
|
||||||
servers.Cyberghost.Servers = a.GetCyberghost()
|
servers.Cyberghost.Servers = a.GetCyberghost()
|
||||||
|
servers.Expressvpn.Servers = a.GetExpressvpn()
|
||||||
servers.Fastestvpn.Servers = a.GetFastestvpn()
|
servers.Fastestvpn.Servers = a.GetFastestvpn()
|
||||||
servers.HideMyAss.Servers = a.GetHideMyAss()
|
servers.HideMyAss.Servers = a.GetHideMyAss()
|
||||||
servers.Ipvanish.Servers = a.GetIpvanish()
|
servers.Ipvanish.Servers = a.GetIpvanish()
|
||||||
@@ -38,6 +39,18 @@ func (a *AllServers) GetCyberghost() (servers []CyberghostServer) {
|
|||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AllServers) GetExpressvpn() (servers []ExpressvpnServer) {
|
||||||
|
if a.Expressvpn.Servers == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
servers = make([]ExpressvpnServer, len(a.Expressvpn.Servers))
|
||||||
|
for i, serverToCopy := range a.Expressvpn.Servers {
|
||||||
|
servers[i] = serverToCopy
|
||||||
|
servers[i].IPs = copyIPs(serverToCopy.IPs)
|
||||||
|
}
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AllServers) GetFastestvpn() (servers []FastestvpnServer) {
|
func (a *AllServers) GetFastestvpn() (servers []FastestvpnServer) {
|
||||||
if a.Fastestvpn.Servers == nil {
|
if a.Fastestvpn.Servers == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ func Test_AllServers_GetCopy(t *testing.T) {
|
|||||||
IPs: []net.IP{{1, 2, 3, 4}},
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
Expressvpn: ExpressvpnServers{
|
||||||
|
Servers: []ExpressvpnServer{{
|
||||||
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
Fastestvpn: FastestvpnServers{
|
Fastestvpn: FastestvpnServers{
|
||||||
Servers: []FastestvpnServer{{
|
Servers: []FastestvpnServer{{
|
||||||
IPs: []net.IP{{1, 2, 3, 4}},
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
|
|||||||
@@ -30,6 +30,20 @@ func (s CyberghostServer) ToMarkdown() (markdown string) {
|
|||||||
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
|
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ExpressvpnServers) ToMarkdown() (markdown string) {
|
||||||
|
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
|
||||||
|
for _, server := range s.Servers {
|
||||||
|
markdown += server.ToMarkdown() + "\n"
|
||||||
|
}
|
||||||
|
return markdown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ExpressvpnServer) ToMarkdown() (markdown string) {
|
||||||
|
return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
|
||||||
|
s.Country, s.City, s.Hostname,
|
||||||
|
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FastestvpnServers) ToMarkdown() (markdown string) {
|
func (s *FastestvpnServers) ToMarkdown() (markdown string) {
|
||||||
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
|
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
|
||||||
for _, server := range s.Servers {
|
for _, server := range s.Servers {
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ type CyberghostServer struct {
|
|||||||
IPs []net.IP `json:"ips"`
|
IPs []net.IP `json:"ips"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExpressvpnServer struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
TCP bool `json:"tcp"`
|
||||||
|
UDP bool `json:"udp"`
|
||||||
|
IPs []net.IP `json:"ips"`
|
||||||
|
}
|
||||||
|
|
||||||
type FastestvpnServer struct {
|
type FastestvpnServer struct {
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
TCP bool `json:"tcp"`
|
TCP bool `json:"tcp"`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package models
|
|||||||
type AllServers struct {
|
type AllServers struct {
|
||||||
Version uint16 `json:"version"` // used for migration of the top level scheme
|
Version uint16 `json:"version"` // used for migration of the top level scheme
|
||||||
Cyberghost CyberghostServers `json:"cyberghost"`
|
Cyberghost CyberghostServers `json:"cyberghost"`
|
||||||
|
Expressvpn ExpressvpnServers `json:"expressvpn"`
|
||||||
Fastestvpn FastestvpnServers `json:"fastestvpn"`
|
Fastestvpn FastestvpnServers `json:"fastestvpn"`
|
||||||
HideMyAss HideMyAssServers `json:"hidemyass"`
|
HideMyAss HideMyAssServers `json:"hidemyass"`
|
||||||
Ipvanish IpvanishServers `json:"ipvanish"`
|
Ipvanish IpvanishServers `json:"ipvanish"`
|
||||||
@@ -24,6 +25,7 @@ type AllServers struct {
|
|||||||
|
|
||||||
func (a *AllServers) Count() int {
|
func (a *AllServers) Count() int {
|
||||||
return len(a.Cyberghost.Servers) +
|
return len(a.Cyberghost.Servers) +
|
||||||
|
len(a.Expressvpn.Servers) +
|
||||||
len(a.Fastestvpn.Servers) +
|
len(a.Fastestvpn.Servers) +
|
||||||
len(a.HideMyAss.Servers) +
|
len(a.HideMyAss.Servers) +
|
||||||
len(a.Ipvanish.Servers) +
|
len(a.Ipvanish.Servers) +
|
||||||
@@ -48,6 +50,11 @@ type CyberghostServers struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Servers []CyberghostServer `json:"servers"`
|
Servers []CyberghostServer `json:"servers"`
|
||||||
}
|
}
|
||||||
|
type ExpressvpnServers struct {
|
||||||
|
Version uint16 `json:"version"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Servers []ExpressvpnServer `json:"servers"`
|
||||||
|
}
|
||||||
type FastestvpnServers struct {
|
type FastestvpnServers struct {
|
||||||
Version uint16 `json:"version"`
|
Version uint16 `json:"version"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
|||||||
44
internal/provider/expressvpn/connection.go
Normal file
44
internal/provider/expressvpn/connection.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) GetConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.Connection, err error) {
|
||||||
|
port := getPort(selection)
|
||||||
|
protocol := utils.GetProtocol(selection)
|
||||||
|
|
||||||
|
servers, err := p.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,
|
||||||
|
Hostname: server.Hostname,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickConnection(connections, selection, p.randSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPort(selection configuration.ServerSelection) (port uint16) {
|
||||||
|
const (
|
||||||
|
defaultOpenVPNTCP = 0
|
||||||
|
defaultOpenVPNUDP = 1195
|
||||||
|
defaultWireguard = 0
|
||||||
|
)
|
||||||
|
return utils.GetPort(selection, defaultOpenVPNTCP,
|
||||||
|
defaultOpenVPNUDP, defaultWireguard)
|
||||||
|
}
|
||||||
97
internal/provider/expressvpn/connection_test.go
Normal file
97
internal/provider/expressvpn/connection_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
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_Provider_GetConnection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
servers []models.ExpressvpnServer
|
||||||
|
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.ExpressvpnServer{
|
||||||
|
{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)}, UDP: true},
|
||||||
|
},
|
||||||
|
connection: models.Connection{
|
||||||
|
IP: net.IPv4(1, 1, 1, 1),
|
||||||
|
Port: 1195,
|
||||||
|
Protocol: constants.UDP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"target IP": {
|
||||||
|
selection: configuration.ServerSelection{
|
||||||
|
TargetIP: net.IPv4(2, 2, 2, 2),
|
||||||
|
},
|
||||||
|
servers: []models.ExpressvpnServer{
|
||||||
|
{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)}, UDP: true},
|
||||||
|
},
|
||||||
|
connection: models.Connection{
|
||||||
|
IP: net.IPv4(2, 2, 2, 2),
|
||||||
|
Port: 1195,
|
||||||
|
Protocol: constants.UDP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"with filter": {
|
||||||
|
selection: configuration.ServerSelection{
|
||||||
|
Hostnames: []string{"b"},
|
||||||
|
},
|
||||||
|
servers: []models.ExpressvpnServer{
|
||||||
|
{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)}, UDP: true},
|
||||||
|
},
|
||||||
|
connection: models.Connection{
|
||||||
|
IP: net.IPv4(2, 2, 2, 2),
|
||||||
|
Port: 1195,
|
||||||
|
Protocol: constants.UDP,
|
||||||
|
Hostname: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
28
internal/provider/expressvpn/filter.go
Normal file
28
internal/provider/expressvpn/filter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.ExpressvpnServer, err error) {
|
||||||
|
for _, server := range p.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
utils.FilterByProtocol(selection, server.TCP, server.UDP):
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
119
internal/provider/expressvpn/filter_test.go
Normal file
119
internal/provider/expressvpn/filter_test.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
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_Expressvpn_filterServers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
servers []models.ExpressvpnServer
|
||||||
|
selection configuration.ServerSelection
|
||||||
|
filtered []models.ExpressvpnServer
|
||||||
|
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.ExpressvpnServer{
|
||||||
|
{Hostname: "a", UDP: true},
|
||||||
|
{Hostname: "b", UDP: true},
|
||||||
|
{Hostname: "c", UDP: true},
|
||||||
|
},
|
||||||
|
filtered: []models.ExpressvpnServer{
|
||||||
|
{Hostname: "a", UDP: true},
|
||||||
|
{Hostname: "b", UDP: true},
|
||||||
|
{Hostname: "c", UDP: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filter by country": {
|
||||||
|
selection: configuration.ServerSelection{
|
||||||
|
Countries: []string{"b"},
|
||||||
|
},
|
||||||
|
servers: []models.ExpressvpnServer{
|
||||||
|
{Country: "a", UDP: true},
|
||||||
|
{Country: "b", UDP: true},
|
||||||
|
{Country: "c", UDP: true},
|
||||||
|
},
|
||||||
|
filtered: []models.ExpressvpnServer{
|
||||||
|
{Country: "b", UDP: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filter by city": {
|
||||||
|
selection: configuration.ServerSelection{
|
||||||
|
Cities: []string{"b"},
|
||||||
|
},
|
||||||
|
servers: []models.ExpressvpnServer{
|
||||||
|
{City: "a", UDP: true},
|
||||||
|
{City: "b", UDP: true},
|
||||||
|
{City: "c", UDP: true},
|
||||||
|
},
|
||||||
|
filtered: []models.ExpressvpnServer{
|
||||||
|
{City: "b", UDP: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filter by hostname": {
|
||||||
|
selection: configuration.ServerSelection{
|
||||||
|
Hostnames: []string{"b"},
|
||||||
|
},
|
||||||
|
servers: []models.ExpressvpnServer{
|
||||||
|
{Hostname: "a", UDP: true},
|
||||||
|
{Hostname: "b", UDP: true},
|
||||||
|
{Hostname: "c", UDP: true},
|
||||||
|
},
|
||||||
|
filtered: []models.ExpressvpnServer{
|
||||||
|
{Hostname: "b", UDP: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filter by protocol": {
|
||||||
|
selection: configuration.ServerSelection{
|
||||||
|
OpenVPN: configuration.OpenVPNSelection{
|
||||||
|
TCP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
servers: []models.ExpressvpnServer{
|
||||||
|
{Hostname: "a", UDP: true},
|
||||||
|
{Hostname: "b", UDP: true, TCP: true},
|
||||||
|
{Hostname: "c", UDP: true},
|
||||||
|
},
|
||||||
|
filtered: []models.ExpressvpnServer{
|
||||||
|
{Hostname: "b", UDP: true, TCP: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
servers, err := m.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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
85
internal/provider/expressvpn/openvpnconf.go
Normal file
85
internal/provider/expressvpn/openvpnconf.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
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 (p *Provider) BuildConf(connection models.Connection,
|
||||||
|
settings configuration.OpenVPN) (lines []string, err error) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA512
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix == 0 {
|
||||||
|
settings.MSSFix = 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"nobind",
|
||||||
|
"tls-exit",
|
||||||
|
"dev " + settings.Interface,
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
|
||||||
|
// Expressvpn specific
|
||||||
|
"fast-io",
|
||||||
|
"fragment 1300",
|
||||||
|
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
||||||
|
"sndbuf 524288",
|
||||||
|
"rcvbuf 524288",
|
||||||
|
"verify-x509-name Server name-prefix", // security hole I guess?
|
||||||
|
"remote-cert-tls server", // updated name of ns-cert-type
|
||||||
|
"key-direction 1",
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"auth-nocache",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
connection.OpenVPNProtoLine(),
|
||||||
|
connection.OpenVPNRemoteLine(),
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||||
|
|
||||||
|
if connection.Protocol == constants.UDP {
|
||||||
|
lines = append(lines, "explicit-exit-notify")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+settings.ProcUser)
|
||||||
|
lines = append(lines, "persist-tun")
|
||||||
|
lines = append(lines, "persist-key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.IPv6 {
|
||||||
|
lines = append(lines, `pull-filter ignore "route-ipv6"`)
|
||||||
|
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCert(
|
||||||
|
constants.ExpressvpnCert)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnRSAKey(
|
||||||
|
constants.ExpressvpnRSAKey)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.ExpressvpnTLSAuthOpenvpnStaticKeyV1)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.ExpressvpnCA)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines, nil
|
||||||
|
}
|
||||||
23
internal/provider/expressvpn/provider.go
Normal file
23
internal/provider/expressvpn/provider.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
servers []models.ExpressvpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
utils.NoPortForwarder
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.ExpressvpnServer, randSource rand.Source) *Provider {
|
||||||
|
return &Provider{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
NoPortForwarder: utils.NewNoPortForwarding(constants.Expressvpn),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/provider/custom"
|
"github.com/qdm12/gluetun/internal/provider/custom"
|
||||||
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/expressvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/fastestvpn"
|
"github.com/qdm12/gluetun/internal/provider/fastestvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/hidemyass"
|
"github.com/qdm12/gluetun/internal/provider/hidemyass"
|
||||||
"github.com/qdm12/gluetun/internal/provider/ipvanish"
|
"github.com/qdm12/gluetun/internal/provider/ipvanish"
|
||||||
@@ -55,6 +56,8 @@ func New(provider string, allServers models.AllServers, timeNow func() time.Time
|
|||||||
return custom.New()
|
return custom.New()
|
||||||
case constants.Cyberghost:
|
case constants.Cyberghost:
|
||||||
return cyberghost.New(allServers.Cyberghost.Servers, randSource)
|
return cyberghost.New(allServers.Cyberghost.Servers, randSource)
|
||||||
|
case constants.Expressvpn:
|
||||||
|
return expressvpn.New(allServers.Expressvpn.Servers, randSource)
|
||||||
case constants.Fastestvpn:
|
case constants.Fastestvpn:
|
||||||
return fastestvpn.New(allServers.Fastestvpn.Servers, randSource)
|
return fastestvpn.New(allServers.Fastestvpn.Servers, randSource)
|
||||||
case constants.HideMyAss:
|
case constants.HideMyAss:
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ func Test_versions(t *testing.T) {
|
|||||||
version: allServers.Cyberghost.Version,
|
version: allServers.Cyberghost.Version,
|
||||||
digest: "9ce64729",
|
digest: "9ce64729",
|
||||||
},
|
},
|
||||||
|
"Expressvpn": {
|
||||||
|
model: models.ExpressvpnServer{},
|
||||||
|
version: allServers.Expressvpn.Version,
|
||||||
|
digest: "6e54a351",
|
||||||
|
},
|
||||||
"Fastestvpn": {
|
"Fastestvpn": {
|
||||||
model: models.FastestvpnServer{},
|
model: models.FastestvpnServer{},
|
||||||
version: allServers.Fastestvpn.Version,
|
version: allServers.Fastestvpn.Version,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
|
|||||||
return models.AllServers{
|
return models.AllServers{
|
||||||
Version: hardcoded.Version,
|
Version: hardcoded.Version,
|
||||||
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
|
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
|
||||||
|
Expressvpn: s.mergeExpressvpn(hardcoded.Expressvpn, persisted.Expressvpn),
|
||||||
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
|
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
|
||||||
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
|
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
|
||||||
Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish),
|
Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish),
|
||||||
@@ -69,6 +70,19 @@ func (s *Storage) mergeCyberghost(hardcoded, persisted models.CyberghostServers)
|
|||||||
return persisted
|
return persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Storage) mergeExpressvpn(hardcoded, persisted models.ExpressvpnServers) models.ExpressvpnServers {
|
||||||
|
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||||
|
return hardcoded
|
||||||
|
}
|
||||||
|
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||||
|
if versionDiff > 0 {
|
||||||
|
s.logVersionDiff("ExpressVPN", versionDiff)
|
||||||
|
return hardcoded
|
||||||
|
}
|
||||||
|
s.logTimeDiff("ExpressVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||||
|
return persisted
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Storage) mergeFastestvpn(hardcoded, persisted models.FastestvpnServers) models.FastestvpnServers {
|
func (s *Storage) mergeFastestvpn(hardcoded, persisted models.FastestvpnServers) models.FastestvpnServers {
|
||||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||||
return hardcoded
|
return hardcoded
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ var (
|
|||||||
|
|
||||||
func countServers(allServers models.AllServers) int {
|
func countServers(allServers models.AllServers) int {
|
||||||
return len(allServers.Cyberghost.Servers) +
|
return len(allServers.Cyberghost.Servers) +
|
||||||
|
len(allServers.Expressvpn.Servers) +
|
||||||
len(allServers.Fastestvpn.Servers) +
|
len(allServers.Fastestvpn.Servers) +
|
||||||
len(allServers.HideMyAss.Servers) +
|
len(allServers.HideMyAss.Servers) +
|
||||||
len(allServers.Ipvanish.Servers) +
|
len(allServers.Ipvanish.Servers) +
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/cyberghost"
|
"github.com/qdm12/gluetun/internal/updater/providers/cyberghost"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/providers/expressvpn"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/fastestvpn"
|
"github.com/qdm12/gluetun/internal/updater/providers/fastestvpn"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/hidemyass"
|
"github.com/qdm12/gluetun/internal/updater/providers/hidemyass"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/ipvanish"
|
"github.com/qdm12/gluetun/internal/updater/providers/ipvanish"
|
||||||
@@ -42,6 +43,28 @@ func (u *updater) updateCyberghost(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updater) updateExpressvpn(ctx context.Context) (err error) {
|
||||||
|
minServers := getMinServers(len(u.servers.Expressvpn.Servers))
|
||||||
|
servers, warnings, err := expressvpn.GetServers(
|
||||||
|
ctx, u.unzipper, u.presolver, minServers)
|
||||||
|
if u.options.CLI {
|
||||||
|
for _, warning := range warnings {
|
||||||
|
u.logger.Warn("ExpressVPN: " + warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.DeepEqual(u.servers.Expressvpn.Servers, servers) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u.servers.Expressvpn.Timestamp = u.timeNow().Unix()
|
||||||
|
u.servers.Expressvpn.Servers = servers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *updater) updateFastestvpn(ctx context.Context) (err error) {
|
func (u *updater) updateFastestvpn(ctx context.Context) (err error) {
|
||||||
minServers := getMinServers(len(u.servers.Fastestvpn.Servers))
|
minServers := getMinServers(len(u.servers.Fastestvpn.Servers))
|
||||||
servers, warnings, err := fastestvpn.GetServers(
|
servers, warnings, err := fastestvpn.GetServers(
|
||||||
|
|||||||
152
internal/updater/providers/expressvpn/hardcoded.go
Normal file
152
internal/updater/providers/expressvpn/hardcoded.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
func hardcodedServers() (servers []models.ExpressvpnServer) {
|
||||||
|
return []models.ExpressvpnServer{
|
||||||
|
{Country: "Albania", Hostname: "albania-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Algeria", Hostname: "algeria-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Andorra", Hostname: "andorra-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Argentina", Hostname: "argentina-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Armenia", Hostname: "armenia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Australia", City: "Brisbane", Hostname: "australia-brisbane-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Australia", City: "Melbourne", Hostname: "australia-melbourne-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Australia", City: "Perth", Hostname: "australia-perth-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Australia", City: "Sydney", Hostname: "australia-sydney-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Australia", City: "Sydney", Hostname: "australia-sydney-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Austria", Hostname: "austria-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Bahamas", Hostname: "bahamas-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Bangladesh", Hostname: "bangladesh-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Belarus", Hostname: "belarus-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Belgium", Hostname: "belgium-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Bhutan", Hostname: "bhutan-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Bosnia And Herzegovina", City: "Bosnia And Herzegovina", Hostname: "bosniaandherzegovina-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Brazil", Hostname: "brazil-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Brazil", Hostname: "brazil-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Brunei", Hostname: "brunei-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Cambodia", Hostname: "cambodia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Canada", City: "Montreal", Hostname: "canada-montreal-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Canada", City: "Montreal", Hostname: "canada-montreal-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Canada", City: "Toronto", Hostname: "canada-toronto-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Canada", City: "Toronto", Hostname: "canada-toronto-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Canada", City: "Vancouver", Hostname: "canada-vancouver-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Chile", Hostname: "chile-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Colombia", Hostname: "colombia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Costa Rica", City: "Costa Rica", Hostname: "costarica-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Croatia", Hostname: "croatia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Cyprus", Hostname: "cyprus-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Czech Republic", City: "Czech Republic", Hostname: "czechrepublic-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Denmark", Hostname: "denmark-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Ecuador", Hostname: "ecuador-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Egypt", Hostname: "egypt-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Estonia", Hostname: "estonia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Finland", Hostname: "finland-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "France", City: "Paris", Hostname: "france-paris-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "France", City: "Paris", Hostname: "france-paris-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "France", City: "Strasbourg", Hostname: "france-strasbourg-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Georgia", Hostname: "georgia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Germany", City: "Frankfurt", Hostname: "germany-frankfurt-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Germany", City: "Frankfurt", Hostname: "germany-frankfurt-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Germany", City: "Frankfurt", Hostname: "germany-darmstadt-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Germany", City: "Nuremberg", Hostname: "germany-nuremberg-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Greece", Hostname: "greece-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Guatemala", Hostname: "guatemala-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hongkong-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hongkong4-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Hungary", Hostname: "hungary-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Iceland", Hostname: "iceland-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "India", City: "Chennai", Hostname: "india-chennai-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "India", City: "Mumbai", Hostname: "india-mumbai-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Indonesia", Hostname: "indonesia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Ireland", Hostname: "ireland-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Isle Of Man", City: "Isle Of Man", Hostname: "isleofman-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Israel", Hostname: "israel-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Italy", City: "Cosenza", Hostname: "italy-cosenza-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Italy", City: "Milan", Hostname: "italy-milan-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Japan", City: "Kawasaki", Hostname: "japan-kawasaki-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Japan", City: "Tokyo", Hostname: "japan-tokyo-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Japan", City: "Tokyo", Hostname: "japan-tokyo-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Jersey", Hostname: "jersey-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Kazakhstan", Hostname: "kazakhstan-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Kenya", Hostname: "kenya-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Kyrgyzstan", Hostname: "kyrgyzstan-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Laos", Hostname: "laos-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Latvia", Hostname: "latvia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Liechtenstein", Hostname: "liechtenstein-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Lithuania", Hostname: "lithuania-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Luxembourg", Hostname: "luxembourg-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Macau", Hostname: "macau-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Malaysia", Hostname: "malaysia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Malta", Hostname: "malta-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Mexico", Hostname: "mexico-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Moldova", Hostname: "moldova-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Monaco", Hostname: "monaco-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Mongolia", Hostname: "mongolia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Montenegro", Hostname: "montenegro-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Myanmar", Hostname: "myanmar-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Nepal", Hostname: "nepal-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Netherlands", City: "Amsterdam", Hostname: "netherlands-amsterdam-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Netherlands", City: "Amsterdam", Hostname: "netherlands-amsterdam-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Netherlands", City: "Rotterdam", Hostname: "netherlands-rotterdam-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Netherlands", City: "The Hague", Hostname: "netherlands-thehague-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "New Zealand", City: "New Zealand", Hostname: "newzealand-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "North Macedonia", City: "North Macedonia", Hostname: "macedonia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Norway", Hostname: "norway-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Pakistan", Hostname: "pakistan-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Panama", Hostname: "panama-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Peru", Hostname: "peru-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Philippines Via Singapore", City: "Philippines Via Singapore", Hostname: "ph-via-sing-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Poland", Hostname: "poland-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Portugal", Hostname: "portugal-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Romania", Hostname: "romania-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Serbia", Hostname: "serbia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Singapore", City: "CBD", Hostname: "singapore-cbd-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Singapore", City: "Jurong", Hostname: "singapore-jurong-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Singapore", City: "Marina Bay", Hostname: "singapore-marinabay-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Slovakia", Hostname: "slovakia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Slovenia", Hostname: "slovenia-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "South Africa", City: "South Africa", Hostname: "southafrica-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "South Korea", City: "South Korea", Hostname: "southkorea2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Spain", City: "Barcelona", Hostname: "spain-barcelona-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Spain", City: "Madrid", Hostname: "spain-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Sri Lanka", City: "Sri Lanka", Hostname: "srilanka-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Sweden", Hostname: "sweden-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Switzerland", Hostname: "switzerland-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Switzerland", Hostname: "switzerland-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Taiwan", Hostname: "taiwan-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Thailand", Hostname: "thailand-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Turkey", Hostname: "turkey-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Ukraine", Hostname: "ukraine-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "UK", City: "Docklands", Hostname: "uk-berkshire-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "UK", City: "London", Hostname: "uk-east-london-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "UK", City: "London", Hostname: "uk-london-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Uruguay", Hostname: "uruguay-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Atlanta", Hostname: "usa-atlanta-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Chicago", Hostname: "usa-chicago-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Dallas", Hostname: "usa-dallas-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Dallas", Hostname: "usa-dallas-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Denver", Hostname: "usa-denver-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-3-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles5-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Miami", Hostname: "usa-miami-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey-3-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "New York", Hostname: "us-new-york-2-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "New York", Hostname: "usa-newyork-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Salt Lake City", Hostname: "usa-saltlakecity-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "San Francisco", Hostname: "usa-sanfrancisco-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Seattle", Hostname: "usa-seattle-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Tampa", Hostname: "usa-tampa-1-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "USA", City: "Washington DC", Hostname: "usa-washingtondc-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Uzbekistan", Hostname: "uzbekistan-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Venezuela", Hostname: "venezuela-ca-version-2.expressnetw.com"},
|
||||||
|
{Country: "Vietnam", Hostname: "vietnam-ca-version-2.expressnetw.com"},
|
||||||
|
}
|
||||||
|
}
|
||||||
30
internal/updater/providers/expressvpn/resolve.go
Normal file
30
internal/updater/providers/expressvpn/resolve.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
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
|
||||||
|
maxNoNew = 1
|
||||||
|
maxFails = 2
|
||||||
|
)
|
||||||
|
settings := resolver.ParallelSettings{
|
||||||
|
MaxFailRatio: maxFailRatio,
|
||||||
|
MinFound: minServers,
|
||||||
|
Repeat: resolver.RepeatSettings{
|
||||||
|
MaxDuration: time.Second,
|
||||||
|
MaxNoNew: maxNoNew,
|
||||||
|
MaxFails: maxFails,
|
||||||
|
SortIPs: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return presolver.Resolve(ctx, hosts, settings)
|
||||||
|
}
|
||||||
54
internal/updater/providers/expressvpn/servers.go
Normal file
54
internal/updater/providers/expressvpn/servers.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// package expressvpn contains code to obtain the server information
|
||||||
|
// for the ExpressVPN provider.
|
||||||
|
package expressvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotEnoughServers = errors.New("not enough servers found")
|
||||||
|
|
||||||
|
func GetServers(ctx context.Context, unzipper unzip.Unzipper,
|
||||||
|
presolver resolver.Parallel, minServers int) (
|
||||||
|
servers []models.ExpressvpnServer, warnings []string, err error) {
|
||||||
|
servers = hardcodedServers()
|
||||||
|
|
||||||
|
hosts := make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
hosts[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
hostToIPs, newWarnings, err := resolveHosts(ctx, presolver, hosts, minServers)
|
||||||
|
warnings = append(warnings, newWarnings...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for _, server := range servers {
|
||||||
|
hostname := server.Hostname
|
||||||
|
server.IPs = hostToIPs[hostname]
|
||||||
|
if len(server.IPs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
server.UDP = true // no TCP support
|
||||||
|
servers[i] = server
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
servers = servers[:i]
|
||||||
|
|
||||||
|
if len(servers) < minServers {
|
||||||
|
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
|
ErrNotEnoughServers, len(servers), minServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
sortServers(servers)
|
||||||
|
|
||||||
|
return servers, warnings, nil
|
||||||
|
}
|
||||||
19
internal/updater/providers/expressvpn/sort.go
Normal file
19
internal/updater/providers/expressvpn/sort.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package expressvpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sortServers(servers []models.ExpressvpnServer) {
|
||||||
|
sort.Slice(servers, func(i, j int) bool {
|
||||||
|
if servers[i].Country == servers[j].Country {
|
||||||
|
if servers[i].City == servers[j].City {
|
||||||
|
return servers[i].Hostname < servers[j].Hostname
|
||||||
|
}
|
||||||
|
return servers[i].City < servers[j].City
|
||||||
|
}
|
||||||
|
return servers[i].Country < servers[j].Country
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -61,6 +61,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.options.Expressvpn {
|
||||||
|
u.logger.Info("updating Expressvpn servers...")
|
||||||
|
if err := u.updateExpressvpn(ctx); err != nil {
|
||||||
|
u.logger.Error(err.Error())
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return allServers, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if u.options.Fastestvpn {
|
if u.options.Fastestvpn {
|
||||||
u.logger.Info("updating Fastestvpn servers...")
|
u.logger.Info("updating Fastestvpn servers...")
|
||||||
if err := u.updateFastestvpn(ctx); err != nil {
|
if err := u.updateFastestvpn(ctx); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user