Feature: FastestVPN support (#383)

This commit is contained in:
Quentin McGaw
2021-03-05 23:12:19 -05:00
committed by GitHub
parent 9509b855f1
commit 9f4077d35d
21 changed files with 587 additions and 18 deletions

3
.github/labels.yml vendored
View File

@@ -21,6 +21,9 @@
- name: ":cloud: HideMyAss" - name: ":cloud: HideMyAss"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: FastestVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Mullvad" - name: ":cloud: Mullvad"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -1,6 +1,6 @@
# Gluetun VPN client # Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, *Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN, HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers
using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
@@ -39,7 +39,7 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
## Features ## Features
- Based on Alpine 3.12 for a small Docker image of 52MB - Based on Alpine 3.12 for a small Docker image of 52MB
- Supports: **Cyberghost**, **HideMyAss**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe**, servers - Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe**, servers
- Supports Openvpn only for now - Supports Openvpn only for now
- DNS over TLS baked in with service provider(s) of your choice - DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours

View File

@@ -23,6 +23,7 @@ func (c *cli) Update(ctx context.Context, args []string, os os.OS) error {
flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)") flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)")
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(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers") flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost 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.Mullvad, "mullvad", false, "Update Mullvad servers") flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers") flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")

View File

@@ -0,0 +1,43 @@
package configuration
import (
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) fastestvpnLines() (lines []string) {
if len(settings.ServerSelection.Hostnames) > 0 {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
}
if len(settings.ServerSelection.Countries) > 0 {
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
}
return lines
}
func (settings *Provider) readFastestvpn(r reader) (err error) {
settings.Name = constants.Fastestvpn
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.FastestvpnHostnameChoices())
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.FastestvpnCountriesChoices())
if err != nil {
return err
}
return nil
}

View File

@@ -56,8 +56,8 @@ var (
func (settings *OpenVPN) read(r reader) (err error) { func (settings *OpenVPN) read(r reader) (err error) {
vpnsp, err := r.env.Inside("VPNSP", []string{ vpnsp, err := r.env.Inside("VPNSP", []string{
"cyberghost", "hidemyass", "mullvad", "nordvpn", "privado", "cyberghost", "fastestvpn", "hidemyass", "mullvad", "nordvpn",
"pia", "private internet access", "privatevpn", "privado", "pia", "private internet access", "privatevpn",
"purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"}, "purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"},
params.Default("private internet access")) params.Default("private internet access"))
if err != nil { if err != nil {
@@ -115,6 +115,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
switch settings.Provider.Name { switch settings.Provider.Name {
case constants.Cyberghost: case constants.Cyberghost:
readProvider = settings.Provider.readCyberghost readProvider = settings.Provider.readCyberghost
case constants.Fastestvpn:
readProvider = settings.Provider.readFastestvpn
case constants.HideMyAss: case constants.HideMyAss:
readProvider = settings.Provider.readHideMyAss readProvider = settings.Provider.readHideMyAss
case constants.Mullvad: case constants.Mullvad:

View File

@@ -31,6 +31,8 @@ func (settings *Provider) lines() (lines []string) {
switch strings.ToLower(settings.Name) { switch strings.ToLower(settings.Name) {
case "cyberghost": case "cyberghost":
providerLines = settings.cyberghostLines() providerLines = settings.cyberghostLines()
case "fastestvpn":
providerLines = settings.fastestvpnLines()
case "hidemyass": case "hidemyass":
providerLines = settings.hideMyAssLines() providerLines = settings.hideMyAssLines()
case "mullvad": case "mullvad":

View File

@@ -42,6 +42,22 @@ func Test_Provider_lines(t *testing.T) {
" |--Client certificate is set", " |--Client certificate is set",
}, },
}, },
"fastestvpn": {
settings: Provider{
Name: constants.Fastestvpn,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Hostnames: []string{"a", "b"},
Countries: []string{"c", "d"},
},
},
lines: []string{
"|--Fastestvpn settings:",
" |--Network protocol: udp",
" |--Hostnames: a, b",
" |--Countries: c, d",
},
},
"hidemyass": { "hidemyass": {
settings: Provider{ settings: Provider{
Name: constants.HideMyAss, Name: constants.HideMyAss,

View File

@@ -15,9 +15,9 @@ type ServerSelection struct {
// Cyberghost // Cyberghost
Group string `json:"group"` Group string `json:"group"`
Countries []string `json:"countries"` // HideMyAss, Mullvad, PrivateVPN, PureVPN Countries []string `json:"countries"` // Fastestvpn, HideMyAss, Mullvad, PrivateVPN, PureVPN
Cities []string `json:"cities"` // HideMyAss, Mullvad, PrivateVPN, PureVPN, Windscribe Cities []string `json:"cities"` // HideMyAss, Mullvad, PrivateVPN, PureVPN, Windscribe
Hostnames []string `json:"hostnames"` // HideMyAss, PrivateVPN, Windscribe, Privado Hostnames []string `json:"hostnames"` // Fastestvpn, HideMyAss, PrivateVPN, Windscribe, Privado
// Mullvad // Mullvad
ISPs []string `json:"isps"` ISPs []string `json:"isps"`

View File

@@ -11,6 +11,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"`
Fastestvpn bool `json:"fastestvpn"`
HideMyAss bool `json:"hidemyass"` HideMyAss bool `json:"hidemyass"`
Mullvad bool `json:"mullvad"` Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"` Nordvpn bool `json:"nordvpn"`

View File

@@ -0,0 +1,117 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
FastestvpnCertificate = "MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq"
FastestvpnOpenvpnStaticKeyV1 = "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42"
)
func FastestvpnCountriesChoices() (choices []string) {
servers := FastestvpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return choices
}
func FastestvpnHostnameChoices() (choices []string) {
servers := FastestvpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return choices
}
// FastestvpnServers returns the list of all VPN servers for FastestVPN.
//nolint:lll
func FastestvpnServers() []models.FastestvpnServer {
return []models.FastestvpnServer{
{Country: "Australia", Hostname: "au-sd-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{139, 99, 149, 10}}},
{Country: "Australia", Hostname: "au-sd-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{139, 99, 149, 10}}},
{Country: "Australia", Hostname: "au2-sd-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{139, 99, 131, 126}}},
{Country: "Australia", Hostname: "au2-sd-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{139, 99, 131, 126}}},
{Country: "Austria", Hostname: "at.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{86, 107, 21, 146}}},
{Country: "Belgium", Hostname: "bel1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{217, 138, 211, 67}}},
{Country: "Belgium", Hostname: "bel2.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{217, 138, 211, 68}}},
{Country: "Belgium", Hostname: "bel3.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{217, 138, 211, 69}}},
{Country: "Brazil", Hostname: "br-jp-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{45, 179, 88, 31}}},
{Country: "Brazil", Hostname: "br-jp-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{45, 179, 88, 31}}},
{Country: "Bulgaria", Hostname: "bg.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{37, 46, 114, 46}}},
{Country: "Canada", Hostname: "canada.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{158, 69, 26, 75}}},
{Country: "Czechia", Hostname: "cz-pr-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{185, 216, 35, 218}}},
{Country: "Czechia", Hostname: "cz-pr-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{185, 216, 35, 218}}},
{Country: "Denmark", Hostname: "dk.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{185, 245, 84, 70}}},
{Country: "Finland", Hostname: "fi-hs-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{194, 34, 132, 19}}},
{Country: "Finland", Hostname: "fi-hs-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{194, 34, 132, 19}}},
{Country: "France", Hostname: "fr-rb-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{37, 59, 172, 213}}},
{Country: "France", Hostname: "fr-rb-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{37, 59, 172, 213}}},
{Country: "Germany", Hostname: "de1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{83, 143, 245, 254}}},
{Country: "Hong.Kong", Hostname: "hk-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{64, 120, 88, 115}}},
{Country: "Hong.Kong", Hostname: "hk-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{64, 120, 88, 115}}},
{Country: "India", Hostname: "in50.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{103, 104, 74, 32}}},
{Country: "India-Stream", Hostname: "in-stream.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{103, 104, 74, 30}}},
{Country: "Italy", Hostname: "it.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{37, 120, 207, 90}}},
{Country: "Japan", Hostname: "jp-tk-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{202, 239, 38, 147}}},
{Country: "Japan", Hostname: "jp-tk-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{202, 239, 38, 147}}},
{Country: "Luxembourg", Hostname: "lux1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{94, 242, 195, 147}}},
{Country: "Netherlands", Hostname: "nl.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{213, 5, 64, 22}}},
{Country: "Netherlands", Hostname: "nl2.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{89, 46, 223, 251}}},
{Country: "Netherlands", Hostname: "nl3.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{89, 46, 223, 252}}},
{Country: "Norway", Hostname: "nr-ol-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{185, 90, 61, 20}}},
{Country: "Norway", Hostname: "nr-ol-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{185, 90, 61, 20}}},
{Country: "Poland", Hostname: "pl2.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{194, 15, 196, 117}}},
{Country: "Portugal", Hostname: "pt.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{185, 90, 57, 146}}},
{Country: "Romania", Hostname: "ro.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{91, 199, 50, 131}}},
{Country: "Russia", Hostname: "russia.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{95, 213, 193, 52}}},
{Country: "Serbia", Hostname: "rs.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{37, 46, 115, 246}}},
{Country: "Singapore", Hostname: "sg-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{209, 58, 174, 195}}},
{Country: "Singapore", Hostname: "sg-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{209, 58, 174, 195}}},
{Country: "South.Korea", Hostname: "kr-so-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{103, 249, 31, 36}}},
{Country: "South.Korea", Hostname: "kr-so-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{103, 249, 31, 36}}},
{Country: "Spain", Hostname: "es-bl-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{193, 148, 19, 155}}},
{Country: "Spain", Hostname: "es-bl-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{193, 148, 19, 155}}},
{Country: "Sweden", Hostname: "se-st-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{128, 127, 104, 200}}},
{Country: "Sweden", Hostname: "se-st-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{128, 127, 104, 201}}},
{Country: "Sweden", Hostname: "se2.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{79, 142, 76, 142}}},
{Country: "Switzerland", Hostname: "ch-zr-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{82, 102, 24, 254}}},
{Country: "Switzerland", Hostname: "ch-zr-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{82, 102, 24, 254}}},
{Country: "Turkey", Hostname: "tr-iz-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{185, 123, 102, 57}}},
{Country: "Turkey", Hostname: "tr.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{185, 123, 102, 57}}},
{Country: "UAE-Dubai", Hostname: "ue-db-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{45, 9, 249, 110}}},
{Country: "UAE-Dubai", Hostname: "ue-db-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{45, 9, 249, 110}}},
{Country: "UK", Hostname: "uk.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{5, 226, 139, 143}}},
{Country: "UK", Hostname: "uk6.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{5, 226, 139, 148}}},
{Country: "UK-Stream", Hostname: "uk-stream.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{195, 206, 169, 171}}},
{Country: "US-Atlanta", Hostname: "us-at-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{23, 82, 10, 205}}},
{Country: "US-Atlanta", Hostname: "us-at-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{23, 82, 10, 205}}},
{Country: "US-Charlotte", Hostname: "us-cf-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{192, 154, 253, 6}}},
{Country: "US-Charlotte", Hostname: "us-cf-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{192, 154, 253, 6}}},
{Country: "US-Chicago", Hostname: "us-ch1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{174, 34, 154, 209}}},
{Country: "US-Chicago", Hostname: "us-ch2.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{174, 34, 154, 207}}},
{Country: "US-Dallas", Hostname: "us-dl-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{74, 63, 219, 202}}},
{Country: "US-Dallas", Hostname: "us-dl-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{74, 63, 219, 202}}},
{Country: "US-Denver", Hostname: "us-dv1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{173, 248, 157, 107}}},
{Country: "US-Los.Angeles", Hostname: "us-la-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{64, 31, 35, 222}}},
{Country: "US-Los.Angeles", Hostname: "us-la-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{64, 31, 35, 222}}},
{Country: "US-Miami", Hostname: "us-mi-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{162, 255, 138, 231}}},
{Country: "US-Miami", Hostname: "us-mi-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{162, 255, 138, 232}}},
{Country: "US-Netflix", Hostname: "netflix.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{37, 59, 172, 215}}},
{Country: "US-New.York", Hostname: "us-ny-ovtcp-01.jumptoserver.com", UDP: false, TCP: true, IPs: []net.IP{{38, 132, 102, 107}}},
{Country: "US-New.York", Hostname: "us-ny-ovudp-01.jumptoserver.com", UDP: true, TCP: false, IPs: []net.IP{{38, 132, 102, 107}}},
{Country: "US-Phoenix", Hostname: "us-ph1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{23, 83, 184, 71}}},
{Country: "US-Seattle", Hostname: "us-se1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{23, 82, 33, 99}}},
{Country: "US-St.Louis", Hostname: "us-st1.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{148, 72, 173, 28}}},
{Country: "US-St.Louis", Hostname: "us-st3.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{148, 72, 173, 30}}},
{Country: "US-St.Louis", Hostname: "us-st4.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{148, 72, 173, 31}}},
{Country: "US-St.Louis", Hostname: "us-st5.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{148, 72, 173, 32}}},
{Country: "US-Washington", Hostname: "us-wt.jumptoserver.com", UDP: true, TCP: true, IPs: []net.IP{{23, 82, 15, 90}}},
}
}

View File

@@ -11,6 +11,11 @@ func GetAllServers() (allServers models.AllServers) {
Timestamp: 1612031135, // latest takes precedence Timestamp: 1612031135, // latest takes precedence
Servers: CyberghostServers(), Servers: CyberghostServers(),
}, },
Fastestvpn: models.FastestvpnServers{
Version: 1,
Timestamp: 1613323814,
Servers: FastestvpnServers(),
},
HideMyAss: models.HideMyAssServers{ HideMyAss: models.HideMyAssServers{
Version: 1, Version: 1,
Timestamp: 1614562368, Timestamp: 1614562368,

View File

@@ -39,6 +39,11 @@ func Test_versions(t *testing.T) {
version: allServers.Cyberghost.Version, version: allServers.Cyberghost.Version,
digest: "fd6242bb", digest: "fd6242bb",
}, },
"Fastestvpn": {
model: models.FastestvpnServer{},
version: allServers.Fastestvpn.Version,
digest: "8825919b",
},
"HideMyAss": { "HideMyAss": {
model: models.HideMyAssServer{}, model: models.HideMyAssServer{},
version: allServers.HideMyAss.Version, version: allServers.HideMyAss.Version,
@@ -135,6 +140,11 @@ func Test_timestamps(t *testing.T) {
timestamp: allServers.Cyberghost.Timestamp, timestamp: allServers.Cyberghost.Timestamp,
digest: "5d3a8cbf", digest: "5d3a8cbf",
}, },
"Fastestvpn": {
servers: allServers.Fastestvpn.Version,
timestamp: allServers.Fastestvpn.Timestamp,
digest: "da65734a",
},
"HideMyAss": { "HideMyAss": {
servers: allServers.HideMyAss.Servers, servers: allServers.HideMyAss.Servers,
timestamp: allServers.HideMyAss.Timestamp, timestamp: allServers.HideMyAss.Timestamp,

View File

@@ -3,6 +3,8 @@ package constants
const ( const (
// Cyberghost is a VPN provider. // Cyberghost is a VPN provider.
Cyberghost = "cyberghost" Cyberghost = "cyberghost"
// Fastestvpn is a VPN provider.
Fastestvpn = "fastestvpn"
// HideMyAss is a VPN provider. // HideMyAss is a VPN provider.
HideMyAss = "hidemyass" HideMyAss = "hidemyass"
// Mullvad is a VPN provider. // Mullvad is a VPN provider.

View File

@@ -17,6 +17,19 @@ func (s *CyberghostServer) String() string {
return fmt.Sprintf("{Region: %q, Group: %q, IPs: %s}", s.Region, s.Group, goStringifyIPs(s.IPs)) return fmt.Sprintf("{Region: %q, Group: %q, IPs: %s}", s.Region, s.Group, goStringifyIPs(s.IPs))
} }
type FastestvpnServer struct {
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
Country string `json:"country"`
IPs []net.IP `json:"ips"`
}
func (s *FastestvpnServer) String() string {
return fmt.Sprintf("{Country: %q, Hostname: %q, UDP: %t, TCP: %t, IPs: %s}",
s.Country, s.Hostname, s.UDP, s.TCP, goStringifyIPs(s.IPs))
}
type HideMyAssServer struct { type HideMyAssServer struct {
Country string `json:"country"` Country string `json:"country"`
Region string `json:"region"` Region string `json:"region"`
@@ -83,6 +96,18 @@ func (p *PIAServer) String() string {
p.Region, p.ServerName, p.TCP, p.UDP, p.PortForward, goStringifyIP(p.IP)) p.Region, p.ServerName, p.TCP, p.UDP, p.PortForward, goStringifyIP(p.IP))
} }
type PrivatevpnServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
IPs []net.IP `json:"ip"`
}
func (s *PrivatevpnServer) String() string {
return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, IPs: %s}",
s.Country, s.City, s.Hostname, goStringifyIPs(s.IPs))
}
type PurevpnServer struct { type PurevpnServer struct {
Country string `json:"country"` Country string `json:"country"`
Region string `json:"region"` Region string `json:"region"`
@@ -137,18 +162,6 @@ func (s *WindscribeServer) String() string {
s.Region, s.City, s.Hostname, goStringifyIP(s.IP)) s.Region, s.City, s.Hostname, goStringifyIP(s.IP))
} }
type PrivatevpnServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
IPs []net.IP `json:"ip"`
}
func (s *PrivatevpnServer) String() string {
return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, IPs: %s}",
s.Country, s.City, s.Hostname, goStringifyIPs(s.IPs))
}
func goStringifyIP(ip net.IP) string { func goStringifyIP(ip net.IP) string {
s := fmt.Sprintf("%#v", ip) s := fmt.Sprintf("%#v", ip)
s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}") s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}")

View File

@@ -3,6 +3,7 @@ package models
type AllServers struct { type AllServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Cyberghost CyberghostServers `json:"cyberghost"` Cyberghost CyberghostServers `json:"cyberghost"`
Fastestvpn FastestvpnServers `json:"fastestvpn"`
HideMyAss HideMyAssServers `json:"hidemyass"` HideMyAss HideMyAssServers `json:"hidemyass"`
Mullvad MullvadServers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Nordvpn NordvpnServers `json:"nordvpn"` Nordvpn NordvpnServers `json:"nordvpn"`
@@ -18,6 +19,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.Fastestvpn.Servers) +
len(a.HideMyAss.Servers) + len(a.HideMyAss.Servers) +
len(a.Mullvad.Servers) + len(a.Mullvad.Servers) +
len(a.Nordvpn.Servers) + len(a.Nordvpn.Servers) +
@@ -36,6 +38,11 @@ type CyberghostServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []CyberghostServer `json:"servers"` Servers []CyberghostServer `json:"servers"`
} }
type FastestvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []FastestvpnServer `json:"servers"`
}
type HideMyAssServers struct { type HideMyAssServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -0,0 +1,170 @@
package provider
import (
"context"
"fmt"
"math/rand"
"net"
"net/http"
"strconv"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
)
type fastestvpn struct {
servers []models.FastestvpnServer
randSource rand.Source
}
func newFastestvpn(servers []models.FastestvpnServer, timeNow timeNowFunc) *fastestvpn {
return &fastestvpn{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
func (f *fastestvpn) filterServers(countries, hostnames []string, protocol string) (servers []models.FastestvpnServer) {
var tcp, udp bool
if protocol == "tcp" {
tcp = true
} else {
udp = true
}
for _, server := range f.servers {
switch {
case filterByPossibilities(server.Country, countries):
case filterByPossibilities(server.Hostname, hostnames):
case tcp && !server.TCP:
case udp && !server.UDP:
default:
servers = append(servers, server)
}
}
return servers
}
func (f *fastestvpn) notFoundErr(selection configuration.ServerSelection) error {
message := "no server found for protocol " + selection.Protocol
if len(selection.Hostnames) > 0 {
message += " + hostnames " + commaJoin(selection.Hostnames)
}
if len(selection.Countries) > 0 {
message += " + countries " + commaJoin(selection.Countries)
}
return fmt.Errorf(message)
}
func (f *fastestvpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
connection models.OpenVPNConnection, err error) {
var port uint16 = 4443
if selection.TargetIP != nil {
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
}
servers := f.filterServers(selection.Countries, selection.Hostnames, selection.Protocol)
if len(servers) == 0 {
return connection, f.notFoundErr(selection)
}
var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
connection := models.OpenVPNConnection{
IP: IP,
Port: port,
Protocol: selection.Protocol,
}
connections = append(connections, connection)
}
}
return pickRandomConnection(connections, f.randSource), nil
}
func (f *fastestvpn) BuildConf(connection models.OpenVPNConnection,
username string, settings configuration.OpenVPN) (lines []string) {
if len(settings.Cipher) == 0 {
settings.Cipher = aes256cbc
}
if len(settings.Auth) == 0 {
settings.Auth = sha256
}
if settings.MSSFix == 0 {
settings.MSSFix = 1450
}
lines = []string{
"client",
"dev tun",
"nobind",
"persist-key",
"ping 15",
"ping-exit 60",
"ping-timer-rem",
"tls-exit",
// Fastestvpn specific
"ping-restart 0",
"tls-client",
"tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
"comp-lzo",
"key-direction 1",
"tun-mtu 1500",
"tun-mtu-extra 32",
"mssfix " + strconv.Itoa(int(settings.MSSFix)), // defaults to 1450
// Added constant values
"auth-nocache",
"mute-replay-warnings",
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
`pull-filter ignore "ping-restart"`,
"auth-retry nointeract",
"suppress-timestamps",
// Modified variables
"verb " + strconv.Itoa(settings.Verbosity),
"auth-user-pass " + constants.OpenVPNAuthConf,
"proto " + connection.Protocol,
"remote " + connection.IP.String() + " " + strconv.Itoa(int(connection.Port)),
"cipher " + settings.Cipher,
"auth " + settings.Auth,
}
if !settings.Root {
lines = append(lines, "user "+username)
}
lines = append(lines, []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
constants.FastestvpnCertificate,
"-----END CERTIFICATE-----",
"</ca>",
}...)
lines = append(lines, []string{
"<tls-auth>",
"-----BEGIN OpenVPN Static key V1-----",
constants.FastestvpnOpenvpnStaticKeyV1,
"-----END OpenVPN Static key V1-----",
"</tls-auth>",
"",
}...)
return lines
}
func (f *fastestvpn) PortForward(ctx context.Context, client *http.Client,
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath string)) {
panic("port forwarding is not supported for fastestvpn")
}

View File

@@ -27,6 +27,8 @@ func New(provider string, allServers models.AllServers, timeNow timeNowFunc) Pro
switch provider { switch provider {
case constants.Cyberghost: case constants.Cyberghost:
return newCyberghost(allServers.Cyberghost.Servers, timeNow) return newCyberghost(allServers.Cyberghost.Servers, timeNow)
case constants.Fastestvpn:
return newFastestvpn(allServers.Fastestvpn.Servers, timeNow)
case constants.HideMyAss: case constants.HideMyAss:
return newHideMyAss(allServers.HideMyAss.Servers, timeNow) return newHideMyAss(allServers.HideMyAss.Servers, timeNow)
case constants.Mullvad: case constants.Mullvad:

View File

@@ -18,6 +18,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),
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss), HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
@@ -41,6 +42,22 @@ func (s *storage) mergeCyberghost(hardcoded, persisted models.CyberghostServers)
return persisted return persisted
} }
func (s *storage) mergeFastestvpn(hardcoded, persisted models.FastestvpnServers) models.FastestvpnServers {
if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded
}
versionDiff := hardcoded.Version - persisted.Version
if versionDiff > 0 {
s.logger.Info(
"Fastestvpn servers from file discarded because they are %d versions behind",
versionDiff)
return hardcoded
}
s.logger.Info("Using Fastestvpn servers from file (%s more recent)",
getUnixTimeDifference(persisted.Timestamp, hardcoded.Timestamp))
return persisted
}
func (s *storage) mergeHideMyAss(hardcoded, persisted models.HideMyAssServers) models.HideMyAssServers { func (s *storage) mergeHideMyAss(hardcoded, persisted models.HideMyAssServers) models.HideMyAssServers {
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded return hardcoded

View File

@@ -18,6 +18,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.Fastestvpn.Servers) +
len(allServers.HideMyAss.Servers) + len(allServers.HideMyAss.Servers) +
len(allServers.Mullvad.Servers) + len(allServers.Mullvad.Servers) +
len(allServers.Nordvpn.Servers) + len(allServers.Nordvpn.Servers) +

View File

@@ -0,0 +1,147 @@
package updater
import (
"context"
"fmt"
"net/http"
"regexp"
"sort"
"strings"
"github.com/qdm12/gluetun/internal/models"
)
func (u *updater) updateFastestvpn(ctx context.Context) (err error) {
servers, warnings, err := findFastestvpnServersFromZip(ctx, u.client, u.lookupIP)
if u.options.CLI {
for _, warning := range warnings {
u.logger.Warn("FastestVPN: %s", warning)
}
}
if err != nil {
return fmt.Errorf("cannot update FastestVPN servers: %w", err)
}
if u.options.Stdout {
u.println(stringifyFastestVPNServers(servers))
}
u.servers.Fastestvpn.Timestamp = u.timeNow().Unix()
u.servers.Fastestvpn.Servers = servers
return nil
}
func findFastestvpnServersFromZip(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) (
servers []models.FastestvpnServer, warnings []string, err error) {
const zipURL = "https://support.fastestvpn.com/download/openvpn-tcp-udp-config-files"
contents, err := fetchAndExtractFiles(ctx, client, zipURL)
if err != nil {
return nil, nil, err
}
trailNumberExp := regexp.MustCompile(`[0-9]+$`)
type Data struct {
TCP bool
UDP bool
Country string
}
hostToData := make(map[string]Data)
for fileName, content := range contents {
const (
tcpSuffix = "-TCP.ovpn"
udpSuffix = "-UDP.ovpn"
)
var tcp, udp bool
var suffix string
switch {
case strings.HasSuffix(fileName, tcpSuffix):
suffix = tcpSuffix
tcp = true
case strings.HasSuffix(fileName, udpSuffix):
suffix = udpSuffix
udp = true
default:
warning := `filename "` + fileName + `" does not have a protocol suffix`
warnings = append(warnings, warning)
continue
}
countryWithNumber := strings.TrimSuffix(fileName, suffix)
number := trailNumberExp.FindString(countryWithNumber)
country := countryWithNumber[:len(countryWithNumber)-len(number)]
host, warning, err := extractHostFromOVPN(content)
if len(warning) > 0 {
warnings = append(warnings, warning)
}
if err != nil {
// treat error as warning and go to next file
warnings = append(warnings, err.Error()+" in "+fileName)
continue
}
data := hostToData[host]
data.Country = country
if tcp {
data.TCP = true
}
if udp {
data.UDP = true
}
hostToData[host] = data
}
hosts := make([]string, len(hostToData))
i := 0
for host := range hostToData {
hosts[i] = host
i++
}
const repetition = 1
const timeBetween = 0
const failOnErr = true
hostToIPs, _, err := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
if err != nil {
return nil, warnings, err
}
for host, IPs := range hostToIPs {
if len(IPs) == 0 {
warning := fmt.Sprintf("no IP address found for host %q", host)
warnings = append(warnings, warning)
continue
}
data := hostToData[host]
server := models.FastestvpnServer{
Hostname: host,
TCP: data.TCP,
UDP: data.UDP,
Country: data.Country,
IPs: uniqueSortedIPs(IPs),
}
servers = append(servers, server)
}
sort.Slice(servers, func(i, j int) bool {
if servers[i].Country == servers[j].Country {
return servers[i].Hostname < servers[j].Hostname
}
return servers[i].Country < servers[j].Country
})
return servers, warnings, nil
}
func stringifyFastestVPNServers(servers []models.FastestvpnServer) (s string) {
s = "func FastestvpnServers() []models.FastestvpnServer {\n"
s += " return []models.FastestvpnServer{\n"
for _, server := range servers {
s += " " + server.String() + ",\n"
}
s += " }\n"
s += "}"
return s
}

View File

@@ -60,6 +60,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
} }
} }
if u.options.Fastestvpn {
u.logger.Info("updating Fastestvpn servers...")
if err := u.updateFastestvpn(ctx); err != nil {
u.logger.Error(err)
}
if err := ctx.Err(); err != nil {
return allServers, err
}
}
if u.options.HideMyAss { if u.options.HideMyAss {
u.logger.Info("updating HideMyAss servers...") u.logger.Info("updating HideMyAss servers...")
if err := u.updateHideMyAss(ctx); err != nil { if err := u.updateHideMyAss(ctx); err != nil {