From 9f4077d35d8f9a3f8490827b94a32768badb3a4e Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Fri, 5 Mar 2021 23:12:19 -0500 Subject: [PATCH] Feature: FastestVPN support (#383) --- .github/labels.yml | 3 + README.md | 4 +- internal/cli/update.go | 1 + internal/configuration/fastestvpn.go | 43 ++++++ internal/configuration/openvpn.go | 6 +- internal/configuration/provider.go | 2 + internal/configuration/provider_test.go | 16 +++ internal/configuration/selection.go | 4 +- internal/configuration/updater.go | 1 + internal/constants/fastestvpn.go | 117 ++++++++++++++++ internal/constants/servers.go | 5 + internal/constants/servers_test.go | 10 ++ internal/constants/vpn.go | 2 + internal/models/server.go | 37 ++++-- internal/models/servers.go | 7 + internal/provider/fastestvpn.go | 170 ++++++++++++++++++++++++ internal/provider/provider.go | 2 + internal/storage/merge.go | 17 +++ internal/storage/sync.go | 1 + internal/updater/fastestvpn.go | 147 ++++++++++++++++++++ internal/updater/updater.go | 10 ++ 21 files changed, 587 insertions(+), 18 deletions(-) create mode 100644 internal/configuration/fastestvpn.go create mode 100644 internal/constants/fastestvpn.go create mode 100644 internal/provider/fastestvpn.go create mode 100644 internal/updater/fastestvpn.go diff --git a/.github/labels.yml b/.github/labels.yml index d9a74d66..3f9998ad 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -21,6 +21,9 @@ - name: ":cloud: HideMyAss" color: "cfe8d4" description: "" +- name: ":cloud: FastestVPN" + color: "cfe8d4" + description: "" - name: ":cloud: Mullvad" color: "cfe8d4" description: "" diff --git a/README.md b/README.md index 0b3559a4..08ea3c00 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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, PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers 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 - 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 - 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 diff --git a/internal/cli/update.go b/internal/cli/update.go index d603568e..192e63f9 100644 --- a/internal/cli/update.go +++ b/internal/cli/update.go @@ -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.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.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers") flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers") flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers") flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers") diff --git a/internal/configuration/fastestvpn.go b/internal/configuration/fastestvpn.go new file mode 100644 index 00000000..b1a0f2d2 --- /dev/null +++ b/internal/configuration/fastestvpn.go @@ -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 +} diff --git a/internal/configuration/openvpn.go b/internal/configuration/openvpn.go index 284e083b..a29820c2 100644 --- a/internal/configuration/openvpn.go +++ b/internal/configuration/openvpn.go @@ -56,8 +56,8 @@ var ( func (settings *OpenVPN) read(r reader) (err error) { vpnsp, err := r.env.Inside("VPNSP", []string{ - "cyberghost", "hidemyass", "mullvad", "nordvpn", "privado", - "pia", "private internet access", "privatevpn", + "cyberghost", "fastestvpn", "hidemyass", "mullvad", "nordvpn", + "privado", "pia", "private internet access", "privatevpn", "purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"}, params.Default("private internet access")) if err != nil { @@ -115,6 +115,8 @@ func (settings *OpenVPN) read(r reader) (err error) { switch settings.Provider.Name { case constants.Cyberghost: readProvider = settings.Provider.readCyberghost + case constants.Fastestvpn: + readProvider = settings.Provider.readFastestvpn case constants.HideMyAss: readProvider = settings.Provider.readHideMyAss case constants.Mullvad: diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go index 293b29ff..6ae07b2c 100644 --- a/internal/configuration/provider.go +++ b/internal/configuration/provider.go @@ -31,6 +31,8 @@ func (settings *Provider) lines() (lines []string) { switch strings.ToLower(settings.Name) { case "cyberghost": providerLines = settings.cyberghostLines() + case "fastestvpn": + providerLines = settings.fastestvpnLines() case "hidemyass": providerLines = settings.hideMyAssLines() case "mullvad": diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index 3651c9e8..d1ea43ed 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -42,6 +42,22 @@ func Test_Provider_lines(t *testing.T) { " |--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": { settings: Provider{ Name: constants.HideMyAss, diff --git a/internal/configuration/selection.go b/internal/configuration/selection.go index 23c779b0..bdc141bc 100644 --- a/internal/configuration/selection.go +++ b/internal/configuration/selection.go @@ -15,9 +15,9 @@ type ServerSelection struct { // Cyberghost 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 - Hostnames []string `json:"hostnames"` // HideMyAss, PrivateVPN, Windscribe, Privado + Hostnames []string `json:"hostnames"` // Fastestvpn, HideMyAss, PrivateVPN, Windscribe, Privado // Mullvad ISPs []string `json:"isps"` diff --git a/internal/configuration/updater.go b/internal/configuration/updater.go index 132885c8..22f07783 100644 --- a/internal/configuration/updater.go +++ b/internal/configuration/updater.go @@ -11,6 +11,7 @@ type Updater struct { Period time.Duration `json:"period"` DNSAddress string `json:"dns_address"` Cyberghost bool `json:"cyberghost"` + Fastestvpn bool `json:"fastestvpn"` HideMyAss bool `json:"hidemyass"` Mullvad bool `json:"mullvad"` Nordvpn bool `json:"nordvpn"` diff --git a/internal/constants/fastestvpn.go b/internal/constants/fastestvpn.go new file mode 100644 index 00000000..1b21a7da --- /dev/null +++ b/internal/constants/fastestvpn.go @@ -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}}}, + } +} diff --git a/internal/constants/servers.go b/internal/constants/servers.go index e3ee7038..d35cea08 100644 --- a/internal/constants/servers.go +++ b/internal/constants/servers.go @@ -11,6 +11,11 @@ func GetAllServers() (allServers models.AllServers) { Timestamp: 1612031135, // latest takes precedence Servers: CyberghostServers(), }, + Fastestvpn: models.FastestvpnServers{ + Version: 1, + Timestamp: 1613323814, + Servers: FastestvpnServers(), + }, HideMyAss: models.HideMyAssServers{ Version: 1, Timestamp: 1614562368, diff --git a/internal/constants/servers_test.go b/internal/constants/servers_test.go index c4e60bfe..31fd5c82 100644 --- a/internal/constants/servers_test.go +++ b/internal/constants/servers_test.go @@ -39,6 +39,11 @@ func Test_versions(t *testing.T) { version: allServers.Cyberghost.Version, digest: "fd6242bb", }, + "Fastestvpn": { + model: models.FastestvpnServer{}, + version: allServers.Fastestvpn.Version, + digest: "8825919b", + }, "HideMyAss": { model: models.HideMyAssServer{}, version: allServers.HideMyAss.Version, @@ -135,6 +140,11 @@ func Test_timestamps(t *testing.T) { timestamp: allServers.Cyberghost.Timestamp, digest: "5d3a8cbf", }, + "Fastestvpn": { + servers: allServers.Fastestvpn.Version, + timestamp: allServers.Fastestvpn.Timestamp, + digest: "da65734a", + }, "HideMyAss": { servers: allServers.HideMyAss.Servers, timestamp: allServers.HideMyAss.Timestamp, diff --git a/internal/constants/vpn.go b/internal/constants/vpn.go index 4e61e0f7..5b20fb9f 100644 --- a/internal/constants/vpn.go +++ b/internal/constants/vpn.go @@ -3,6 +3,8 @@ package constants const ( // Cyberghost is a VPN provider. Cyberghost = "cyberghost" + // Fastestvpn is a VPN provider. + Fastestvpn = "fastestvpn" // HideMyAss is a VPN provider. HideMyAss = "hidemyass" // Mullvad is a VPN provider. diff --git a/internal/models/server.go b/internal/models/server.go index 96d1c853..720567f7 100644 --- a/internal/models/server.go +++ b/internal/models/server.go @@ -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)) } +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 { Country string `json:"country"` 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)) } +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 { Country string `json:"country"` Region string `json:"region"` @@ -137,18 +162,6 @@ func (s *WindscribeServer) String() string { 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 { s := fmt.Sprintf("%#v", ip) s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}") diff --git a/internal/models/servers.go b/internal/models/servers.go index 8eb59334..ac5d52bc 100644 --- a/internal/models/servers.go +++ b/internal/models/servers.go @@ -3,6 +3,7 @@ package models type AllServers struct { Version uint16 `json:"version"` Cyberghost CyberghostServers `json:"cyberghost"` + Fastestvpn FastestvpnServers `json:"fastestvpn"` HideMyAss HideMyAssServers `json:"hidemyass"` Mullvad MullvadServers `json:"mullvad"` Nordvpn NordvpnServers `json:"nordvpn"` @@ -18,6 +19,7 @@ type AllServers struct { func (a *AllServers) Count() int { return len(a.Cyberghost.Servers) + + len(a.Fastestvpn.Servers) + len(a.HideMyAss.Servers) + len(a.Mullvad.Servers) + len(a.Nordvpn.Servers) + @@ -36,6 +38,11 @@ type CyberghostServers struct { Timestamp int64 `json:"timestamp"` Servers []CyberghostServer `json:"servers"` } +type FastestvpnServers struct { + Version uint16 `json:"version"` + Timestamp int64 `json:"timestamp"` + Servers []FastestvpnServer `json:"servers"` +} type HideMyAssServers struct { Version uint16 `json:"version"` Timestamp int64 `json:"timestamp"` diff --git a/internal/provider/fastestvpn.go b/internal/provider/fastestvpn.go new file mode 100644 index 00000000..33e3ed91 --- /dev/null +++ b/internal/provider/fastestvpn.go @@ -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{ + "", + "-----BEGIN CERTIFICATE-----", + constants.FastestvpnCertificate, + "-----END CERTIFICATE-----", + "", + }...) + + lines = append(lines, []string{ + "", + "-----BEGIN OpenVPN Static key V1-----", + constants.FastestvpnOpenvpnStaticKeyV1, + "-----END OpenVPN Static key V1-----", + "", + "", + }...) + + 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") +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1e39b8c3..9baab92b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -27,6 +27,8 @@ func New(provider string, allServers models.AllServers, timeNow timeNowFunc) Pro switch provider { case constants.Cyberghost: return newCyberghost(allServers.Cyberghost.Servers, timeNow) + case constants.Fastestvpn: + return newFastestvpn(allServers.Fastestvpn.Servers, timeNow) case constants.HideMyAss: return newHideMyAss(allServers.HideMyAss.Servers, timeNow) case constants.Mullvad: diff --git a/internal/storage/merge.go b/internal/storage/merge.go index e1f8fb98..de14d964 100644 --- a/internal/storage/merge.go +++ b/internal/storage/merge.go @@ -18,6 +18,7 @@ func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.Al return models.AllServers{ Version: hardcoded.Version, Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost), + Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn), HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss), Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), @@ -41,6 +42,22 @@ func (s *storage) mergeCyberghost(hardcoded, persisted models.CyberghostServers) 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 { if persisted.Timestamp <= hardcoded.Timestamp { return hardcoded diff --git a/internal/storage/sync.go b/internal/storage/sync.go index e9f11d6e..94ca3933 100644 --- a/internal/storage/sync.go +++ b/internal/storage/sync.go @@ -18,6 +18,7 @@ var ( func countServers(allServers models.AllServers) int { return len(allServers.Cyberghost.Servers) + + len(allServers.Fastestvpn.Servers) + len(allServers.HideMyAss.Servers) + len(allServers.Mullvad.Servers) + len(allServers.Nordvpn.Servers) + diff --git a/internal/updater/fastestvpn.go b/internal/updater/fastestvpn.go new file mode 100644 index 00000000..895576b2 --- /dev/null +++ b/internal/updater/fastestvpn.go @@ -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 +} diff --git a/internal/updater/updater.go b/internal/updater/updater.go index dad21a1f..dc4c1102 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -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 { u.logger.Info("updating HideMyAss servers...") if err := u.updateHideMyAss(ctx); err != nil {