From d81d4bbda33473a8e5647d9acb538dff409bbd55 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Sun, 20 Jun 2021 09:18:03 -0700 Subject: [PATCH] VPN Unlimited support (#499) - Fixes #420 - Revert to docker/build-push-action@v2.4.0 --- .github/labels.yml | 3 + .github/workflows/build.yml | 2 +- README.md | 4 +- internal/cli/update.go | 1 + internal/configuration/cyberghost.go | 54 +- internal/configuration/keys.go | 55 ++ .../{cyberghost_test.go => keys_test.go} | 0 internal/configuration/openvpn.go | 13 +- internal/configuration/openvpn_test.go | 3 +- internal/configuration/provider.go | 2 + internal/configuration/provider_test.go | 25 + internal/configuration/selection.go | 16 +- internal/configuration/torguard.go | 2 +- internal/configuration/updater.go | 36 +- internal/configuration/vpnunlimited.go | 85 +++ internal/configuration/vpnunlimited_test.go | 42 ++ internal/constants/countries.go | 499 +++++++++--------- internal/constants/servers.go | 5 + internal/constants/servers_test.go | 10 + internal/constants/torguard.go | 2 +- internal/constants/vpn.go | 2 + internal/constants/vpnunlimited.go | 124 +++++ internal/models/server.go | 16 + internal/models/servers.go | 39 +- internal/provider/provider.go | 3 + internal/provider/vpnunlimited/connection.go | 44 ++ internal/provider/vpnunlimited/filter.go | 31 ++ internal/provider/vpnunlimited/openvpnconf.go | 69 +++ internal/provider/vpnunlimited/portforward.go | 17 + internal/provider/vpnunlimited/provider.go | 19 + internal/storage/merge.go | 48 +- internal/storage/sync.go | 1 + internal/updater/providers.go | 22 + .../providers/vpnunlimited/constants.go | 160 ++++++ .../providers/vpnunlimited/hosttoserver.go | 38 ++ .../updater/providers/vpnunlimited/resolve.go | 32 ++ .../updater/providers/vpnunlimited/servers.go | 42 ++ .../updater/providers/vpnunlimited/sort.go | 19 + .../updater/providers/vpnunlimited/string.go | 14 + internal/updater/updater.go | 11 + 40 files changed, 1245 insertions(+), 365 deletions(-) create mode 100644 internal/configuration/keys.go rename internal/configuration/{cyberghost_test.go => keys_test.go} (100%) create mode 100644 internal/configuration/vpnunlimited.go create mode 100644 internal/configuration/vpnunlimited_test.go create mode 100644 internal/constants/vpnunlimited.go create mode 100644 internal/provider/vpnunlimited/connection.go create mode 100644 internal/provider/vpnunlimited/filter.go create mode 100644 internal/provider/vpnunlimited/openvpnconf.go create mode 100644 internal/provider/vpnunlimited/portforward.go create mode 100644 internal/provider/vpnunlimited/provider.go create mode 100644 internal/updater/providers/vpnunlimited/constants.go create mode 100644 internal/updater/providers/vpnunlimited/hosttoserver.go create mode 100644 internal/updater/providers/vpnunlimited/resolve.go create mode 100644 internal/updater/providers/vpnunlimited/servers.go create mode 100644 internal/updater/providers/vpnunlimited/sort.go create mode 100644 internal/updater/providers/vpnunlimited/string.go diff --git a/.github/labels.yml b/.github/labels.yml index 74512914..bc12dcac 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -53,6 +53,9 @@ - name: ":cloud: Torguard" color: "cfe8d4" description: "" +- name: ":cloud: VPNUnlimited" + color: "cfe8d4" + description: "" - name: ":cloud: Vyprvpn" color: "cfe8d4" description: "" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 906d68b6..a2d09582 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: fi - name: Build and push final image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v2.4.0 with: platforms: ${{ steps.vars.outputs.platforms }} build-args: | diff --git a/README.md b/README.md index 2fe94ce3..1126a268 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ *Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN, HideMyAss, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN, -ProtonVPN, PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers +ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN and Windscribe VPN servers using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* **ANNOUNCEMENT**: @@ -39,7 +39,7 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* ## Features - Based on Alpine 3.13 for a small Docker image of 54MB -- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe** servers +- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **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 037f61bb..4ffd9996 100644 --- a/internal/cli/update.go +++ b/internal/cli/update.go @@ -43,6 +43,7 @@ func (c *cli) Update(ctx context.Context, args []string, os os.OS, logger loggin flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers") flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers") flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers") + flagSet.BoolVar(&options.VPNUnlimited, "vpnunlimited", false, "Update VPN Unlimited servers") flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers") flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers") if err := flagSet.Parse(args); err != nil { diff --git a/internal/configuration/cyberghost.go b/internal/configuration/cyberghost.go index 8ae322ba..941b5dad 100644 --- a/internal/configuration/cyberghost.go +++ b/internal/configuration/cyberghost.go @@ -1,10 +1,6 @@ package configuration import ( - "encoding/pem" - "errors" - "strings" - "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/golibs/params" ) @@ -44,12 +40,12 @@ func (settings *Provider) readCyberghost(r reader) (err error) { return err } - settings.ExtraConfigOptions.ClientKey, err = readCyberghostClientKey(r) + settings.ExtraConfigOptions.ClientKey, err = readClientKey(r) if err != nil { return err } - settings.ExtraConfigOptions.ClientCertificate, err = readCyberghostClientCertificate(r) + settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r) if err != nil { return err } @@ -72,49 +68,3 @@ func (settings *Provider) readCyberghost(r reader) (err error) { return nil } - -func readCyberghostClientKey(r reader) (clientKey string, err error) { - b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", constants.ClientKey) - if err != nil { - return "", err - } - return extractClientKey(b) -} - -var errDecodePEMBlockClientKey = errors.New("cannot decode PEM block from client key") - -func extractClientKey(b []byte) (key string, err error) { - pemBlock, _ := pem.Decode(b) - if pemBlock == nil { - return "", errDecodePEMBlockClientKey - } - parsedBytes := pem.EncodeToMemory(pemBlock) - s := string(parsedBytes) - s = strings.ReplaceAll(s, "\n", "") - s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----") - s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----") - return s, nil -} - -func readCyberghostClientCertificate(r reader) (clientCertificate string, err error) { - b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", constants.ClientCertificate) - if err != nil { - return "", err - } - return extractClientCertificate(b) -} - -var errDecodePEMBlockClientCert = errors.New("cannot decode PEM block from client certificate") - -func extractClientCertificate(b []byte) (certificate string, err error) { - pemBlock, _ := pem.Decode(b) - if pemBlock == nil { - return "", errDecodePEMBlockClientCert - } - parsedBytes := pem.EncodeToMemory(pemBlock) - s := string(parsedBytes) - s = strings.ReplaceAll(s, "\n", "") - s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----") - s = strings.TrimSuffix(s, "-----END CERTIFICATE-----") - return s, nil -} diff --git a/internal/configuration/keys.go b/internal/configuration/keys.go new file mode 100644 index 00000000..8cf091ce --- /dev/null +++ b/internal/configuration/keys.go @@ -0,0 +1,55 @@ +package configuration + +import ( + "encoding/pem" + "errors" + "strings" + + "github.com/qdm12/gluetun/internal/constants" +) + +func readClientKey(r reader) (clientKey string, err error) { + b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", constants.ClientKey) + if err != nil { + return "", err + } + return extractClientKey(b) +} + +var errDecodePEMBlockClientKey = errors.New("cannot decode PEM block from client key") + +func extractClientKey(b []byte) (key string, err error) { + pemBlock, _ := pem.Decode(b) + if pemBlock == nil { + return "", errDecodePEMBlockClientKey + } + parsedBytes := pem.EncodeToMemory(pemBlock) + s := string(parsedBytes) + s = strings.ReplaceAll(s, "\n", "") + s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----") + s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----") + return s, nil +} + +func readClientCertificate(r reader) (clientCertificate string, err error) { + b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", constants.ClientCertificate) + if err != nil { + return "", err + } + return extractClientCertificate(b) +} + +var errDecodePEMBlockClientCert = errors.New("cannot decode PEM block from client certificate") + +func extractClientCertificate(b []byte) (certificate string, err error) { + pemBlock, _ := pem.Decode(b) + if pemBlock == nil { + return "", errDecodePEMBlockClientCert + } + parsedBytes := pem.EncodeToMemory(pemBlock) + s := string(parsedBytes) + s = strings.ReplaceAll(s, "\n", "") + s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----") + s = strings.TrimSuffix(s, "-----END CERTIFICATE-----") + return s, nil +} diff --git a/internal/configuration/cyberghost_test.go b/internal/configuration/keys_test.go similarity index 100% rename from internal/configuration/cyberghost_test.go rename to internal/configuration/keys_test.go diff --git a/internal/configuration/openvpn.go b/internal/configuration/openvpn.go index ca03bf56..0a6567f7 100644 --- a/internal/configuration/openvpn.go +++ b/internal/configuration/openvpn.go @@ -70,7 +70,7 @@ func (settings *OpenVPN) read(r reader) (err error) { vpnsp, err := r.env.Inside("VPNSP", []string{ "cyberghost", "fastestvpn", "hidemyass", "ivpn", "mullvad", "nordvpn", "privado", "pia", "private internet access", "privatevpn", "protonvpn", - "purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"}, + "purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}, params.Default("private internet access")) if err != nil { return err @@ -86,12 +86,13 @@ func (settings *OpenVPN) read(r reader) (err error) { return err } customConfig := settings.Config != "" - credentialsRequired := true + if customConfig { - credentialsRequired = false settings.Provider.Name = "" } + credentialsRequired := !customConfig && settings.Provider.Name != constants.VPNUnlimited + settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"}) if err != nil { return err @@ -139,7 +140,10 @@ func (settings *OpenVPN) read(r reader) (err error) { return err } settings.MSSFix = uint16(mssFix) + return settings.readProvider(r) +} +func (settings *OpenVPN) readProvider(r reader) error { var readProvider func(r reader) error switch settings.Provider.Name { case "": // custom config @@ -170,6 +174,8 @@ func (settings *OpenVPN) read(r reader) (err error) { readProvider = settings.Provider.readSurfshark case constants.Torguard: readProvider = settings.Provider.readTorguard + case constants.VPNUnlimited: + readProvider = settings.Provider.readVPNUnlimited case constants.Vyprvpn: readProvider = settings.Provider.readVyprvpn case constants.Windscribe: @@ -177,6 +183,5 @@ func (settings *OpenVPN) read(r reader) (err error) { default: return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name) } - return readProvider(r) } diff --git a/internal/configuration/openvpn_test.go b/internal/configuration/openvpn_test.go index 7f7e1421..d66c7f14 100644 --- a/internal/configuration/openvpn_test.go +++ b/internal/configuration/openvpn_test.go @@ -41,7 +41,8 @@ func Test_OpenVPN_JSON(t *testing.T) { "custom_port": 0, "numbers": null, "encryption_preset": "", - "free_only": false + "free_only": false, + "stream_only": false }, "extra_config": { "encryption_preset": "", diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go index 12a6fd8f..e251a3bf 100644 --- a/internal/configuration/provider.go +++ b/internal/configuration/provider.go @@ -56,6 +56,8 @@ func (settings *Provider) lines() (lines []string) { providerLines = settings.surfsharkLines() case "torguard": providerLines = settings.torguardLines() + case strings.ToLower(constants.VPNUnlimited): + providerLines = settings.vpnUnlimitedLines() case "vyprvpn": providerLines = settings.vyprvpnLines() case "windscribe": diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index d5be4363..ab5d0ef4 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -249,6 +249,31 @@ func Test_Provider_lines(t *testing.T) { " |--Hostnames: e", }, }, + constants.VPNUnlimited: { + settings: Provider{ + Name: constants.VPNUnlimited, + ServerSelection: ServerSelection{ + Countries: []string{"a", "b"}, + Cities: []string{"c", "d"}, + Hostnames: []string{"e", "f"}, + FreeOnly: true, + StreamOnly: true, + }, + ExtraConfigOptions: ExtraConfigOptions{ + ClientKey: "a", + }, + }, + lines: []string{ + "|--Vpn Unlimited settings:", + " |--Network protocol: udp", + " |--Countries: a, b", + " |--Cities: c, d", + " |--Hostnames: e, f", + " |--Free servers only", + " |--Stream servers only", + " |--Client key is set", + }, + }, "vyprvpn": { settings: Provider{ Name: constants.Vyprvpn, diff --git a/internal/configuration/selection.go b/internal/configuration/selection.go index 0374b523..aec4f383 100644 --- a/internal/configuration/selection.go +++ b/internal/configuration/selection.go @@ -15,10 +15,13 @@ type ServerSelection struct { //nolint:maligned // Cyberghost Group string `json:"group"` - Countries []string `json:"countries"` // Fastestvpn, HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN - Cities []string `json:"cities"` // HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, Windscribe - Hostnames []string `json:"hostnames"` // Fastestvpn, HideMyAss, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn - Names []string `json:"names"` // Protonvpn + // Fastestvpn, HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited + Countries []string `json:"countries"` + // HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, Windscribe + Cities []string `json:"cities"` + // Fastestvpn, HideMyAss, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited + Hostnames []string `json:"hostnames"` + Names []string `json:"names"` // Protonvpn // Mullvad ISPs []string `json:"isps"` @@ -35,11 +38,14 @@ type ServerSelection struct { //nolint:maligned // ProtonVPN FreeOnly bool `json:"free_only"` + + // VPNUnlimited + StreamOnly bool `json:"stream_only"` } type ExtraConfigOptions struct { ClientCertificate string `json:"-"` // Cyberghost - ClientKey string `json:"-"` // Cyberghost + ClientKey string `json:"-"` // Cyberghost, VPNUnlimited EncryptionPreset string `json:"encryption_preset"` // PIA OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad } diff --git a/internal/configuration/torguard.go b/internal/configuration/torguard.go index a91d8635..d94b3630 100644 --- a/internal/configuration/torguard.go +++ b/internal/configuration/torguard.go @@ -43,7 +43,7 @@ func (settings *Provider) readTorguard(r reader) (err error) { return err } - settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.TorguardHostnamesChoices()) + settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.TorguardHostnameChoices()) if err != nil { return err } diff --git a/internal/configuration/updater.go b/internal/configuration/updater.go index 5b466706..e38e43d2 100644 --- a/internal/configuration/updater.go +++ b/internal/configuration/updater.go @@ -8,23 +8,24 @@ import ( ) 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"` - Ivpn bool `json:"ivpn"` - Mullvad bool `json:"mullvad"` - Nordvpn bool `json:"nordvpn"` - PIA bool `json:"pia"` - Privado bool `json:"privado"` - Privatevpn bool `json:"privatevpn"` - Protonvpn bool `json:"protonvpn"` - Purevpn bool `json:"purevpn"` - Surfshark bool `json:"surfshark"` - Torguard bool `json:"torguard"` - Vyprvpn bool `json:"vyprvpn"` - Windscribe bool `json:"windscribe"` + Period time.Duration `json:"period"` + DNSAddress string `json:"dns_address"` + Cyberghost bool `json:"cyberghost"` + Fastestvpn bool `json:"fastestvpn"` + HideMyAss bool `json:"hidemyass"` + Ivpn bool `json:"ivpn"` + Mullvad bool `json:"mullvad"` + Nordvpn bool `json:"nordvpn"` + PIA bool `json:"pia"` + Privado bool `json:"privado"` + Privatevpn bool `json:"privatevpn"` + Protonvpn bool `json:"protonvpn"` + Purevpn bool `json:"purevpn"` + Surfshark bool `json:"surfshark"` + Torguard bool `json:"torguard"` + VPNUnlimited bool `json:"vpnunlimited"` + Vyprvpn bool `json:"vyprvpn"` + Windscribe bool `json:"windscribe"` // The two below should be used in CLI mode only Stdout bool `json:"-"` // in order to update constants file (maintainer side) CLI bool `json:"-"` @@ -60,6 +61,7 @@ func (settings *Updater) read(r reader) (err error) { settings.Purevpn = true settings.Surfshark = true settings.Torguard = true + settings.VPNUnlimited = true settings.Vyprvpn = true settings.Windscribe = true settings.Stdout = false diff --git a/internal/configuration/vpnunlimited.go b/internal/configuration/vpnunlimited.go new file mode 100644 index 00000000..fb857809 --- /dev/null +++ b/internal/configuration/vpnunlimited.go @@ -0,0 +1,85 @@ +package configuration + +import ( + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) vpnUnlimitedLines() (lines []string) { + if len(settings.ServerSelection.Countries) > 0 { + lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries)) + } + + if len(settings.ServerSelection.Cities) > 0 { + lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities)) + } + + if len(settings.ServerSelection.Hostnames) > 0 { + lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames)) + } + + if settings.ServerSelection.FreeOnly { + lines = append(lines, lastIndent+"Free servers only") + } + + if settings.ServerSelection.StreamOnly { + lines = append(lines, lastIndent+"Stream servers only") + } + + if settings.ExtraConfigOptions.ClientKey != "" { + lines = append(lines, lastIndent+"Client key is set") + } + + return lines +} + +func (settings *Provider) readVPNUnlimited(r reader) (err error) { + settings.Name = constants.VPNUnlimited + + settings.ServerSelection.TCP, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ExtraConfigOptions.ClientKey, err = readClientKey(r) + if err != nil { + return err + } + + settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r) + if err != nil { + return err + } + + settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.IvpnCountryChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IvpnCityChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices()) + if err != nil { + return err + } + + settings.ServerSelection.FreeOnly, err = r.env.YesNo("FREE_ONLY", params.Default("no")) + if err != nil { + return err + } + + settings.ServerSelection.StreamOnly, err = r.env.YesNo("STREAM_ONLY", params.Default("no")) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/vpnunlimited_test.go b/internal/configuration/vpnunlimited_test.go new file mode 100644 index 00000000..5622463e --- /dev/null +++ b/internal/configuration/vpnunlimited_test.go @@ -0,0 +1,42 @@ +package configuration + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Provider_vpnUnlimitedLines(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + settings Provider + lines []string + }{ + "empty settings": {}, + "full settings": { + settings: Provider{ + ServerSelection: ServerSelection{ + Countries: []string{"A", "B"}, + Cities: []string{"C", "D"}, + Hostnames: []string{"E", "F"}, + }, + }, + lines: []string{ + "|--Countries: A, B", + "|--Cities: C, D", + "|--Hostnames: E, F", + }, + }, + } + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + lines := testCase.settings.vpnUnlimitedLines() + + assert.Equal(t, testCase.lines, lines) + }) + } +} diff --git a/internal/constants/countries.go b/internal/constants/countries.go index d69cbd7b..2a88746d 100644 --- a/internal/constants/countries.go +++ b/internal/constants/countries.go @@ -2,254 +2,255 @@ package constants func CountryCodes() map[string]string { return map[string]string{ - "af": "Afghanistan", - "ax": "Aland Islands", - "al": "Albania", - "dz": "Algeria", - "as": "American Samoa", - "ad": "Andorra", - "ao": "Angola", - "ai": "Anguilla", - "aq": "Antarctica", - "ag": "Antigua and Barbuda", - "ar": "Argentina", - "am": "Armenia", - "aw": "Aruba", - "au": "Australia", - "at": "Austria", - "az": "Azerbaijan", - "bs": "Bahamas", - "bh": "Bahrain", - "bd": "Bangladesh", - "bb": "Barbados", - "by": "Belarus", - "be": "Belgium", - "bz": "Belize", - "bj": "Benin", - "bm": "Bermuda", - "bt": "Bhutan", - "bo": "Bolivia", - "bq": "Bonaire", - "ba": "Bosnia and Herzegovina", - "bw": "Botswana", - "bv": "Bouvet Island", - "br": "Brazil", - "io": "British Indian Ocean Territory", - "vg": "British Virgin Islands", - "bn": "Brunei Darussalam", - "bg": "Bulgaria", - "bf": "Burkina Faso", - "bi": "Burundi", - "kh": "Cambodia", - "cm": "Cameroon", - "ca": "Canada", - "cv": "Cape Verde", - "ky": "Cayman Islands", - "cf": "Central African Republic", - "td": "Chad", - "cl": "Chile", - "cn": "China", - "cx": "Christmas Island", - "cc": "Cocos Islands", - "co": "Colombia", - "km": "Comoros", - "cg": "Congo", - "ck": "Cook Islands", - "cr": "Costa Rica", - "ci": "Cote d'Ivoire", - "hr": "Croatia", - "cu": "Cuba", - "cw": "Curacao", - "cy": "Cyprus", - "cz": "Czech Republic", - "cd": "Democratic Republic of the Congo", - "dk": "Denmark", - "dj": "Djibouti", - "dm": "Dominica", - "do": "Dominican Republic", - "ec": "Ecuador", - "eg": "Egypt", - "sv": "El Salvador", - "gq": "Equatorial Guinea", - "er": "Eritrea", - "ee": "Estonia", - "et": "Ethiopia", - "fk": "Falkland Islands", - "fo": "Faroe Islands", - "fj": "Fiji", - "fi": "Finland", - "fr": "France", - "gf": "French Guiana", - "pf": "French Polynesia", - "tf": "French Southern Territories", - "ga": "Gabon", - "gm": "Gambia", - "ge": "Georgia", - "de": "Germany", - "gh": "Ghana", - "gi": "Gibraltar", - "gr": "Greece", - "gl": "Greenland", - "gd": "Grenada", - "gp": "Guadeloupe", - "gu": "Guam", - "gt": "Guatemala", - "gg": "Guernsey", - "gw": "Guinea-Bissau", - "gn": "Guinea", - "gy": "Guyana", - "ht": "Haiti", - "hm": "Heard Island and McDonald Islands", - "hn": "Honduras", - "hk": "Hong Kong", - "hu": "Hungary", - "is": "Iceland", - "in": "India", - "id": "Indonesia", - "ir": "Iran", - "iq": "Iraq", - "ie": "Ireland", - "im": "Isle of Man", - "il": "Israel", - "it": "Italy", - "jm": "Jamaica", - "jp": "Japan", - "je": "Jersey", - "jo": "Jordan", - "kz": "Kazakhstan", - "ke": "Kenya", - "ki": "Kiribati", - "kr": "Korea", - "kw": "Kuwait", - "kg": "Kyrgyzstan", - "la": "Lao People's Democratic Republic", - "lv": "Latvia", - "lb": "Lebanon", - "ls": "Lesotho", - "lr": "Liberia", - "ly": "Libya", - "li": "Liechtenstein", - "lt": "Lithuania", - "lu": "Luxembourg", - "mo": "Macao", - "mk": "Macedonia", - "mg": "Madagascar", - "mw": "Malawi", - "my": "Malaysia", - "mv": "Maldives", - "ml": "Mali", - "mt": "Malta", - "mh": "Marshall Islands", - "mq": "Martinique", - "mr": "Mauritania", - "mu": "Mauritius", - "yt": "Mayotte", - "mx": "Mexico", - "fm": "Micronesia", - "md": "Moldova", - "mc": "Monaco", - "mn": "Mongolia", - "me": "Montenegro", - "ms": "Montserrat", - "ma": "Morocco", - "mz": "Mozambique", - "mm": "Myanmar", - "na": "Namibia", - "nr": "Nauru", - "np": "Nepal", - "nl": "Netherlands", - "nc": "New Caledonia", - "nz": "New Zealand", - "ni": "Nicaragua", - "ne": "Niger", - "ng": "Nigeria", - "nu": "Niue", - "nf": "Norfolk Island", - "mp": "Northern Mariana Islands", - "no": "Norway", - "om": "Oman", - "pk": "Pakistan", - "pw": "Palau", - "ps": "Palestine, State of", - "pa": "Panama", - "pg": "Papua New Guinea", - "py": "Paraguay", - "pe": "Peru", - "ph": "Philippines", - "pn": "Pitcairn", - "pl": "Poland", - "pt": "Portugal", - "pr": "Puerto Rico", - "qa": "Qatar", - "re": "Reunion", - "ro": "Romania", - "ru": "Russian Federation", - "rw": "Rwanda", - "bl": "Saint Barthelemy", - "sh": "Saint Helena", - "kn": "Saint Kitts and Nevis", - "lc": "Saint Lucia", - "mf": "Saint Martin", - "pm": "Saint Pierre and Miquelon", - "vc": "Saint Vincent and the Grenadines", - "ws": "Samoa", - "sm": "San Marino", - "st": "Sao Tome and Principe", - "sa": "Saudi Arabia", - "sn": "Senegal", - "rs": "Serbia", - "sc": "Seychelles", - "sl": "Sierra Leone", - "sg": "Singapore", - "sx": "Sint Maarten", - "sk": "Slovakia", - "si": "Slovenia", - "sb": "Solomon Islands", - "so": "Somalia", - "za": "South Africa", - "gs": "South Georgia and the South Sandwich Islands", - "ss": "South Sudan", - "es": "Spain", - "lk": "Sri Lanka", - "sd": "Sudan", - "sr": "Suriname", - "sj": "Svalbard and Jan Mayen", - "sz": "Swaziland", - "se": "Sweden", - "ch": "Switzerland", - "sy": "Syrian Arab Republic", - "tw": "Taiwan", - "tj": "Tajikistan", - "tz": "Tanzania", - "th": "Thailand", - "tl": "Timor-Leste", - "tg": "Togo", - "tk": "Tokelau", - "to": "Tonga", - "tt": "Trinidad and Tobago", - "tn": "Tunisia", - "tr": "Turkey", - "tm": "Turkmenistan", - "tc": "Turks and Caicos Islands", - "tv": "Tuvalu", - "ug": "Uganda", - "ua": "Ukraine", - "ae": "United Arab Emirates", - "gb": "United Kingdom", - "uk": "United Kingdom", - "um": "United States Minor Outlying Islands", - "us": "United States", - "uy": "Uruguay", - "vi": "US Virgin Islands", - "uz": "Uzbekistan", - "vu": "Vanuatu", - "va": "Vatican City State", - "ve": "Venezuela", - "vn": "Vietnam", - "wf": "Wallis and Futuna", - "eh": "Western Sahara", - "ye": "Yemen", - "zm": "Zambia", - "zw": "Zimbabwe", + "af": "Afghanistan", + "ax": "Aland Islands", + "al": "Albania", + "dz": "Algeria", + "as": "American Samoa", + "ad": "Andorra", + "ao": "Angola", + "ai": "Anguilla", + "aq": "Antarctica", + "ag": "Antigua and Barbuda", + "ar": "Argentina", + "am": "Armenia", + "aw": "Aruba", + "au": "Australia", + "at": "Austria", + "az": "Azerbaijan", + "bs": "Bahamas", + "bh": "Bahrain", + "bd": "Bangladesh", + "bb": "Barbados", + "by": "Belarus", + "be": "Belgium", + "bz": "Belize", + "bj": "Benin", + "bm": "Bermuda", + "bt": "Bhutan", + "bo": "Bolivia", + "bq": "Bonaire", + "ba": "Bosnia and Herzegovina", + "bw": "Botswana", + "bv": "Bouvet Island", + "br": "Brazil", + "io": "British Indian Ocean Territory", + "vg": "British Virgin Islands", + "bn": "Brunei Darussalam", + "bg": "Bulgaria", + "bf": "Burkina Faso", + "bi": "Burundi", + "kh": "Cambodia", + "cm": "Cameroon", + "ca": "Canada", + "cv": "Cape Verde", + "ky": "Cayman Islands", + "cf": "Central African Republic", + "td": "Chad", + "cl": "Chile", + "cn": "China", + "cx": "Christmas Island", + "cc": "Cocos Islands", + "co": "Colombia", + "km": "Comoros", + "cg": "Congo", + "ck": "Cook Islands", + "cr": "Costa Rica", + "ci": "Cote d'Ivoire", + "hr": "Croatia", + "cu": "Cuba", + "cw": "Curacao", + "cy": "Cyprus", + "cz": "Czech Republic", + "cd": "Democratic Republic of the Congo", + "dk": "Denmark", + "dj": "Djibouti", + "dm": "Dominica", + "do": "Dominican Republic", + "ec": "Ecuador", + "eg": "Egypt", + "sv": "El Salvador", + "gq": "Equatorial Guinea", + "er": "Eritrea", + "ee": "Estonia", + "et": "Ethiopia", + "fk": "Falkland Islands", + "fo": "Faroe Islands", + "fj": "Fiji", + "fi": "Finland", + "fr": "France", + "gf": "French Guiana", + "pf": "French Polynesia", + "tf": "French Southern Territories", + "ga": "Gabon", + "gm": "Gambia", + "ge": "Georgia", + "de": "Germany", + "gh": "Ghana", + "gi": "Gibraltar", + "gr": "Greece", + "gl": "Greenland", + "gd": "Grenada", + "gp": "Guadeloupe", + "gu": "Guam", + "gt": "Guatemala", + "gg": "Guernsey", + "gw": "Guinea-Bissau", + "gn": "Guinea", + "gy": "Guyana", + "ht": "Haiti", + "hm": "Heard Island and McDonald Islands", + "hn": "Honduras", + "hk": "Hong Kong", + "hu": "Hungary", + "is": "Iceland", + "in": "India", + "id": "Indonesia", + "ir": "Iran", + "iq": "Iraq", + "ie": "Ireland", + "im": "Isle of Man", + "il": "Israel", + "it": "Italy", + "jm": "Jamaica", + "jp": "Japan", + "je": "Jersey", + "jo": "Jordan", + "kz": "Kazakhstan", + "ke": "Kenya", + "ki": "Kiribati", + "kr": "Korea", + "kw": "Kuwait", + "kg": "Kyrgyzstan", + "la": "Lao People's Democratic Republic", + "lv": "Latvia", + "lb": "Lebanon", + "ls": "Lesotho", + "lr": "Liberia", + "ly": "Libya", + "li": "Liechtenstein", + "lt": "Lithuania", + "lu": "Luxembourg", + "mo": "Macao", + "mk": "Macedonia", + "mg": "Madagascar", + "mw": "Malawi", + "my": "Malaysia", + "mys": "Kuala Lumpur", + "mv": "Maldives", + "ml": "Mali", + "mt": "Malta", + "mh": "Marshall Islands", + "mq": "Martinique", + "mr": "Mauritania", + "mu": "Mauritius", + "yt": "Mayotte", + "mx": "Mexico", + "fm": "Micronesia", + "md": "Moldova", + "mc": "Monaco", + "mn": "Mongolia", + "me": "Montenegro", + "ms": "Montserrat", + "ma": "Morocco", + "mz": "Mozambique", + "mm": "Myanmar", + "na": "Namibia", + "nr": "Nauru", + "np": "Nepal", + "nl": "Netherlands", + "nc": "New Caledonia", + "nz": "New Zealand", + "ni": "Nicaragua", + "ne": "Niger", + "ng": "Nigeria", + "nu": "Niue", + "nf": "Norfolk Island", + "mp": "Northern Mariana Islands", + "no": "Norway", + "om": "Oman", + "pk": "Pakistan", + "pw": "Palau", + "ps": "Palestine, State of", + "pa": "Panama", + "pg": "Papua New Guinea", + "py": "Paraguay", + "pe": "Peru", + "ph": "Philippines", + "pn": "Pitcairn", + "pl": "Poland", + "pt": "Portugal", + "pr": "Puerto Rico", + "qa": "Qatar", + "re": "Reunion", + "ro": "Romania", + "ru": "Russian Federation", + "rw": "Rwanda", + "bl": "Saint Barthelemy", + "sh": "Saint Helena", + "kn": "Saint Kitts and Nevis", + "lc": "Saint Lucia", + "mf": "Saint Martin", + "pm": "Saint Pierre and Miquelon", + "vc": "Saint Vincent and the Grenadines", + "ws": "Samoa", + "sm": "San Marino", + "st": "Sao Tome and Principe", + "sa": "Saudi Arabia", + "sn": "Senegal", + "rs": "Serbia", + "sc": "Seychelles", + "sl": "Sierra Leone", + "sg": "Singapore", + "sx": "Sint Maarten", + "sk": "Slovakia", + "si": "Slovenia", + "sb": "Solomon Islands", + "so": "Somalia", + "za": "South Africa", + "gs": "South Georgia and the South Sandwich Islands", + "ss": "South Sudan", + "es": "Spain", + "lk": "Sri Lanka", + "sd": "Sudan", + "sr": "Suriname", + "sj": "Svalbard and Jan Mayen", + "sz": "Swaziland", + "se": "Sweden", + "ch": "Switzerland", + "sy": "Syrian Arab Republic", + "tw": "Taiwan", + "tj": "Tajikistan", + "tz": "Tanzania", + "th": "Thailand", + "tl": "Timor-Leste", + "tg": "Togo", + "tk": "Tokelau", + "to": "Tonga", + "tt": "Trinidad and Tobago", + "tn": "Tunisia", + "tr": "Turkey", + "tm": "Turkmenistan", + "tc": "Turks and Caicos Islands", + "tv": "Tuvalu", + "ug": "Uganda", + "ua": "Ukraine", + "ae": "United Arab Emirates", + "gb": "United Kingdom", + "uk": "United Kingdom", + "um": "United States Minor Outlying Islands", + "us": "United States", + "uy": "Uruguay", + "vi": "US Virgin Islands", + "uz": "Uzbekistan", + "vu": "Vanuatu", + "va": "Vatican City State", + "ve": "Venezuela", + "vn": "Vietnam", + "wf": "Wallis and Futuna", + "eh": "Western Sahara", + "ye": "Yemen", + "zm": "Zambia", + "zw": "Zimbabwe", } } diff --git a/internal/constants/servers.go b/internal/constants/servers.go index 970eca2e..51ab3aeb 100644 --- a/internal/constants/servers.go +++ b/internal/constants/servers.go @@ -71,6 +71,11 @@ func GetAllServers() (allServers models.AllServers) { Timestamp: 1620611129, Servers: TorguardServers(), }, + VPNUnlimited: models.VPNUnlimitedServers{ + Version: 1, + Timestamp: 1623950304, + Servers: VPNUnlimitedServers(), + }, Vyprvpn: models.VyprvpnServers{ Version: 2, Timestamp: 1620612506, diff --git a/internal/constants/servers_test.go b/internal/constants/servers_test.go index 9339aecd..6e36cf92 100644 --- a/internal/constants/servers_test.go +++ b/internal/constants/servers_test.go @@ -100,6 +100,11 @@ func Test_versions(t *testing.T) { version: allServers.Torguard.Version, digest: "6eb9028e", }, + "VPN Unlimited": { + model: models.VPNUnlimitedServer{}, + version: allServers.VPNUnlimited.Version, + digest: "5cb51319", + }, "Vyprvpn": { model: models.VyprvpnServer{}, version: allServers.Vyprvpn.Version, @@ -212,6 +217,11 @@ func Test_timestamps(t *testing.T) { timestamp: allServers.Torguard.Timestamp, digest: "af54b9e8", }, + "VPN Unlimited": { + servers: allServers.VPNUnlimited.Servers, + timestamp: allServers.VPNUnlimited.Timestamp, + digest: "f6ddb84c", + }, "Vyprvpn": { servers: allServers.Vyprvpn.Servers, timestamp: allServers.Vyprvpn.Timestamp, diff --git a/internal/constants/torguard.go b/internal/constants/torguard.go index 67668671..365eb27c 100644 --- a/internal/constants/torguard.go +++ b/internal/constants/torguard.go @@ -30,7 +30,7 @@ func TorguardCityChoices() (choices []string) { return makeUnique(choices) } -func TorguardHostnamesChoices() (choices []string) { +func TorguardHostnameChoices() (choices []string) { servers := TorguardServers() choices = make([]string, len(servers)) for i := range servers { diff --git a/internal/constants/vpn.go b/internal/constants/vpn.go index b821830c..e06ae328 100644 --- a/internal/constants/vpn.go +++ b/internal/constants/vpn.go @@ -27,6 +27,8 @@ const ( Surfshark = "surfshark" // Torguard is a VPN provider. Torguard = "torguard" + // VPNUnlimited is a VPN provider. + VPNUnlimited = "vpn unlimited" // Vyprvpn is a VPN provider. Vyprvpn = "vyprvpn" // Windscribe is a VPN provider. diff --git a/internal/constants/vpnunlimited.go b/internal/constants/vpnunlimited.go new file mode 100644 index 00000000..b88ad0d5 --- /dev/null +++ b/internal/constants/vpnunlimited.go @@ -0,0 +1,124 @@ +package constants + +import ( + "net" + + "github.com/qdm12/gluetun/internal/models" +) + +//nolint:lll +const ( + VPNUnlimitedCertificateAuthority = "MIIEjjCCA/egAwIBAgIJAKsVbHBdakXuMA0GCSqGSIb3DQEBBQUAMIHgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMR8wHQYDVQQKExZTaW1wbGV4IFNvbHV0aW9ucyBJbmMuMRYwFAYDVQQLEw1WcG4gVW5saW1pdGVkMSMwIQYDVQQDExpzZXJ2ZXIudnBudW5saW1pdGVkYXBwLmNvbTEjMCEGA1UEKRMac2VydmVyLnZwbnVubGltaXRlZGFwcC5jb20xLjAsBgkqhkiG9w0BCQEWH3N1cHBvcnRAc2ltcGxleHNvbHV0aW9uc2luYy5jb20wHhcNMTMxMjE2MTM1OTQ0WhcNMjMxMjE0MTM1OTQ0WjCB4DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMREwDwYDVQQHEwhOZXcgWW9yazEfMB0GA1UEChMWU2ltcGxleCBTb2x1dGlvbnMgSW5jLjEWMBQGA1UECxMNVnBuIFVubGltaXRlZDEjMCEGA1UEAxMac2VydmVyLnZwbnVubGltaXRlZGFwcC5jb20xIzAhBgNVBCkTGnNlcnZlci52cG51bmxpbWl0ZWRhcHAuY29tMS4wLAYJKoZIhvcNAQkBFh9zdXBwb3J0QHNpbXBsZXhzb2x1dGlvbnNpbmMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDADUzS8QWGvdhLFKsEzAiq5+b0ukKjBza0k6/dmCeYTvCVqGKg/h1IAtQdVVLAkmEp0zvGH7PuOhXm7zZrCouBr/RiG4tEcoRHwc6AJmowkYERlY7+xGx3OuNrD00QceNTsan0bn78jwt0zhFNmHdoTtFjgK3oqmQYSAtbEVWYgwIDAQABo4IBTDCCAUgwHQYDVR0OBBYEFKClmYP+tMNgWagUJCCHjtaui2k+MIIBFwYDVR0jBIIBDjCCAQqAFKClmYP+tMNgWagUJCCHjtaui2k+oYHmpIHjMIHgMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMR8wHQYDVQQKExZTaW1wbGV4IFNvbHV0aW9ucyBJbmMuMRYwFAYDVQQLEw1WcG4gVW5saW1pdGVkMSMwIQYDVQQDExpzZXJ2ZXIudnBudW5saW1pdGVkYXBwLmNvbTEjMCEGA1UEKRMac2VydmVyLnZwbnVubGltaXRlZGFwcC5jb20xLjAsBgkqhkiG9w0BCQEWH3N1cHBvcnRAc2ltcGxleHNvbHV0aW9uc2luYy5jb22CCQCrFWxwXWpF7jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBALkWhfw7SSV7it+ZYZmT+cQbExjlYgQ40zae2J2CqIYACRcfsDHvh7Q+fiwSocevv2NE0dWi6WB2H6SiudYjvDvubAX/QbzfBxtbxCCoAIlfPCm8xOnWFN7TUJAzWwHJkKgEnu29GZHu2x8J+7VeDbKH5RTMHHe8FkSxh6Zz/BMN" +) + +func VPNUnlimitedCountryChoices() (choices []string) { + servers := VPNUnlimitedServers() + choices = make([]string, len(servers)) + for i := range servers { + choices[i] = servers[i].Country + } + return makeUnique(choices) +} + +func VPNUnlimitedCityChoices() (choices []string) { + servers := VPNUnlimitedServers() + choices = make([]string, len(servers)) + for i := range servers { + choices[i] = servers[i].City + } + return makeUnique(choices) +} + +func VPNUnlimitedHostnameChoices() (choices []string) { + servers := VPNUnlimitedServers() + choices = make([]string, len(servers)) + for i := range servers { + choices[i] = servers[i].Hostname + } + return makeUnique(choices) +} + +//nolint:lll +// VPNUnlimitedServers returns a slice of all the server information for VPNUnlimited. +func VPNUnlimitedServers() []models.VPNUnlimitedServer { + return []models.VPNUnlimitedServer{ + {Country: "Argentina", City: "", Hostname: "ar.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{131, 255, 4, 187}, {131, 255, 4, 235}}}, + {Country: "Australia", City: "Sydney", Hostname: "au-syd.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{139, 99, 131, 38}, {139, 99, 130, 220}}}, + {Country: "Austria", City: "", Hostname: "at.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{50, 7, 115, 19}, {185, 210, 219, 194}, {185, 210, 219, 198}}}, + {Country: "Belarus", City: "", Hostname: "by.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 66, 68, 23}, {185, 66, 71, 108}, {185, 66, 71, 115}, {185, 66, 68, 84}, {185, 66, 71, 22}, {185, 66, 71, 122}, {185, 66, 71, 8}, {185, 66, 68, 18}}}, + {Country: "Belgium", City: "", Hostname: "be.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{37, 120, 143, 178}, {82, 102, 19, 110}}}, + {Country: "Bosnia and Herzegovina", City: "", Hostname: "ba.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 164, 35, 37}}}, + {Country: "Brazil", City: "", Hostname: "br.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{66, 90, 70, 7}, {177, 67, 82, 222}, {177, 67, 82, 218}, {177, 67, 82, 228}, {177, 67, 82, 221}, {177, 67, 82, 219}, {177, 67, 82, 210}, {177, 67, 82, 223}}}, + {Country: "Bulgaria", City: "", Hostname: "bg.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{37, 120, 152, 26}}}, + {Country: "Canada", City: "", Hostname: "ca.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{192, 99, 7, 170}, {192, 99, 6, 164}, {192, 99, 14, 158}, {192, 99, 37, 199}, {192, 99, 6, 166}}}, + {Country: "Canada", City: "Toronto", Hostname: "ca-tr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{184, 75, 213, 154}, {162, 253, 131, 106}, {162, 219, 176, 163}, {104, 254, 90, 58}, {104, 254, 92, 42}, {184, 75, 213, 194}, {204, 187, 100, 82}, {104, 254, 90, 34}}}, + {Country: "Canada", City: "Vancouver", Hostname: "ca-vn.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{162, 221, 202, 138}, {162, 221, 202, 134}, {162, 221, 202, 17}}}, + {Country: "Costa Rica", City: "", Hostname: "cr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{138, 59, 19, 8}}}, + {Country: "Croatia", City: "", Hostname: "hr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{85, 10, 56, 3}, {85, 10, 56, 100}, {85, 10, 51, 3}, {85, 10, 56, 4}}}, + {Country: "Cyprus", City: "", Hostname: "cy.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{194, 30, 136, 123}, {194, 30, 136, 102}}}, + {Country: "Czech Republic", City: "", Hostname: "cz.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 216, 35, 46}}}, + {Country: "Denmark", City: "", Hostname: "dk.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{37, 120, 194, 174}, {89, 45, 7, 90}}}, + {Country: "Estonia", City: "", Hostname: "ee.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 155, 96, 132}}}, + {Country: "Finland", City: "", Hostname: "fi.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 112, 82, 26}, {91, 233, 116, 44}}}, + {Country: "France", City: "", Hostname: "fr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{195, 154, 189, 85}, {195, 154, 204, 36}, {195, 154, 211, 84}, {62, 210, 38, 83}, {195, 154, 169, 66}, {195, 154, 166, 20}, {62, 210, 204, 161}, {62, 210, 205, 16}, {62, 210, 139, 124}, {195, 154, 221, 54}, {195, 154, 199, 175}, {195, 154, 189, 212}, {195, 154, 180, 96}, {195, 154, 199, 155}, {195, 154, 209, 149}, {195, 154, 222, 168}}}, + {Country: "France", City: "Roubaix", Hostname: "fr-rbx.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{151, 80, 27, 199}, {51, 255, 71, 16}, {147, 135, 137, 107}}}, + {Country: "Germany", City: "", Hostname: "de.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{84, 16, 236, 168}, {178, 162, 205, 77}, {178, 162, 205, 115}, {178, 162, 205, 82}, {84, 16, 238, 220}}}, + {Country: "Germany", City: "Düsseldorf", Hostname: "de-dus.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{146, 0, 42, 77}, {146, 0, 32, 121}, {85, 14, 243, 42}}}, + {Country: "Greece", City: "", Hostname: "gr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{154, 57, 3, 36}}}, + {Country: "Hungary", City: "", Hostname: "hu.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{91, 219, 238, 174}}}, + {Country: "Iceland", City: "", Hostname: "is.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{37, 235, 49, 75}, {151, 236, 24, 114}, {37, 235, 49, 42}, {37, 235, 49, 244}, {37, 235, 49, 70}, {37, 235, 49, 15}, {151, 236, 24, 142}}}, + {Country: "India", City: "", Hostname: "in.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{103, 26, 204, 46}, {103, 26, 204, 84}}}, + {Country: "India", City: "Karnataka", Hostname: "in-ka.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{139, 59, 67, 224}, {139, 59, 65, 136}, {139, 59, 24, 197}, {139, 59, 24, 198}, {139, 59, 24, 199}, {139, 59, 24, 191}, {139, 59, 16, 78}}}, + {Country: "Ireland", City: "Dublin", Hostname: "ie-dub.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{188, 241, 178, 118}}}, + {Country: "Isle of Man", City: "", Hostname: "im.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{192, 71, 211, 15}, {37, 235, 55, 62}, {37, 235, 55, 68}, {37, 235, 55, 14}, {37, 235, 55, 45}}}, + {Country: "Israel", City: "", Hostname: "il.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{193, 182, 144, 170}, {193, 182, 144, 151}, {193, 182, 144, 23}}}, + {Country: "Italy", City: "Milan", Hostname: "it-mil.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{91, 193, 5, 50}}}, + {Country: "Japan", City: "", Hostname: "jp.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{172, 104, 67, 80}, {172, 104, 75, 121}, {161, 202, 97, 109}, {172, 104, 71, 35}, {172, 104, 126, 64}, {172, 104, 64, 213}, {85, 208, 110, 122}, {172, 104, 78, 67}, {172, 104, 83, 34}, {172, 104, 99, 172}, {161, 202, 97, 101}, {172, 104, 83, 13}, {172, 104, 87, 163}, {172, 104, 99, 14}}}, + {Country: "Korea", City: "", Hostname: "kr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{160, 202, 162, 60}, {160, 202, 163, 122}}}, + {Country: "Kuala Lumpur", City: "", Hostname: "mys.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{111, 90, 141, 34}, {111, 90, 158, 159}}}, + {Country: "Latvia", City: "", Hostname: "lv.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{195, 123, 213, 172}, {195, 123, 209, 168}}}, + {Country: "Libya", City: "", Hostname: "ly.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{41, 208, 71, 39}}}, + {Country: "Lithuania", City: "", Hostname: "lt.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 64, 105, 93}, {185, 64, 104, 142}, {185, 25, 48, 81}}}, + {Country: "Luxembourg", City: "", Hostname: "lu.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{94, 242, 246, 45}, {94, 242, 246, 46}, {94, 242, 246, 77}, {94, 242, 246, 38}, {94, 242, 246, 47}}}, + {Country: "Mexico", City: "", Hostname: "mx.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{169, 57, 35, 104}}}, + {Country: "Moldova", City: "", Hostname: "md.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 163, 46, 141}}}, + {Country: "Netherlands", City: "", Hostname: "nl.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{190, 2, 132, 115}, {190, 2, 132, 16}, {190, 2, 132, 52}}}, + {Country: "New Zealand", City: "", Hostname: "nz.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{103, 75, 119, 109}, {103, 75, 119, 107}, {103, 75, 119, 105}, {103, 75, 119, 102}, {103, 75, 119, 103}, {103, 75, 119, 104}, {103, 75, 119, 106}, {103, 75, 119, 108}, {103, 75, 119, 101}, {103, 75, 119, 11}}}, + {Country: "Norway", City: "", Hostname: "no.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 90, 61, 21}, {185, 90, 61, 74}}}, + {Country: "Oman", City: "", Hostname: "om.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 226, 124, 110}}}, + {Country: "Poland", City: "", Hostname: "pl.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{37, 120, 156, 234}}}, + {Country: "Portugal", City: "", Hostname: "pt.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 31, 159, 110}}}, + {Country: "Romania", City: "", Hostname: "ro.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 144, 83, 11}, {93, 115, 92, 207}, {185, 144, 83, 13}, {77, 81, 98, 70}, {93, 115, 92, 208}}}, + {Country: "Serbia", City: "", Hostname: "rs.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{152, 89, 160, 142}}}, + {Country: "Singapore", City: "", Hostname: "sg-free.vpnunlimitedapp.com", Free: true, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{178, 128, 48, 177}, {188, 166, 177, 236}, {206, 189, 80, 158}, {178, 128, 117, 139}}}, + {Country: "Singapore", City: "", Hostname: "sg.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{50, 7, 60, 52}, {117, 20, 41, 9}, {117, 20, 41, 10}, {139, 99, 62, 61}}}, + {Country: "Slovakia", City: "", Hostname: "sk.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{46, 29, 2, 131}}}, + {Country: "Slovenia", City: "", Hostname: "si.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{192, 71, 244, 38}, {192, 71, 244, 28}}}, + {Country: "South Africa", City: "", Hostname: "za.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{129, 232, 130, 187}, {129, 232, 219, 196}, {129, 232, 133, 41}, {129, 232, 129, 157}, {129, 232, 219, 195}, {129, 232, 134, 122}, {129, 232, 130, 186}, {129, 232, 130, 179}}}, + {Country: "Spain", City: "", Hostname: "es.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{194, 99, 104, 58}, {195, 206, 107, 134}}}, + {Country: "Sweden", City: "", Hostname: "se.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{91, 132, 138, 174}}}, + {Country: "Switzerland", City: "", Hostname: "ch.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{152, 89, 162, 90}, {152, 89, 162, 86}}}, + {Country: "Thailand", City: "", Hostname: "th.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{103, 159, 3, 121}}}, + {Country: "Turkey", City: "", Hostname: "tr.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 17, 115, 62}, {185, 73, 202, 218}}}, + {Country: "United Arab Emirates", City: "", Hostname: "ae.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{45, 9, 249, 201}, {45, 9, 249, 209}, {45, 9, 250, 147}, {45, 9, 250, 249}, {45, 9, 250, 138}, {45, 9, 249, 211}, {45, 9, 250, 144}, {45, 9, 249, 216}, {45, 9, 250, 145}, {45, 9, 250, 143}, {45, 9, 250, 134}, {45, 9, 250, 146}, {45, 9, 249, 202}}}, + {Country: "United Kingdom", City: "", Hostname: "uk.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{176, 227, 198, 122}, {77, 245, 65, 2}, {109, 73, 77, 18}, {5, 152, 213, 186}, {88, 150, 224, 74}, {88, 150, 180, 82}}}, + {Country: "United Kingdom", City: "London", Hostname: "uk-cv.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{5, 101, 169, 146}, {5, 101, 143, 66}, {178, 159, 10, 78}}}, + {Country: "United Kingdom", City: "London", Hostname: "uk-lon.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{78, 110, 160, 6}, {50, 7, 114, 220}, {5, 101, 136, 154}, {23, 106, 33, 89}}}, + {Country: "United States", City: "", Hostname: "us-stream.vpnunlimitedapp.com", Free: false, Stream: true, TCP: false, UDP: true, IPs: []net.IP{{167, 99, 96, 113}, {198, 199, 114, 155}, {192, 241, 194, 208}, {165, 227, 49, 171}, {159, 89, 128, 183}, {138, 197, 203, 142}, {64, 227, 111, 143}, {143, 110, 225, 79}, {164, 90, 144, 44}, {198, 199, 103, 243}, {198, 199, 96, 52}, {128, 199, 9, 51}, {143, 110, 156, 9}, {178, 128, 79, 75}, {198, 199, 97, 247}, {198, 199, 105, 87}, {198, 199, 103, 88}, {206, 189, 211, 205}, {206, 189, 165, 44}, {161, 35, 236, 242}}}, + {Country: "United States", City: "", Hostname: "us.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{199, 115, 117, 81}, {199, 115, 115, 139}, {207, 244, 72, 212}, {199, 115, 115, 160}, {199, 115, 117, 73}}}, + {Country: "United States", City: "Chicago", Hostname: "us-chi.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{66, 23, 205, 226}, {108, 62, 202, 211}, {173, 234, 41, 130}, {174, 34, 184, 130}}}, + {Country: "United States", City: "Dallas", Hostname: "us-dal.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{172, 241, 115, 99}, {172, 241, 113, 19}, {172, 241, 112, 92}}}, + {Country: "United States", City: "Denver", Hostname: "us-den.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{23, 237, 26, 131}, {23, 237, 26, 67}}}, + {Country: "United States", City: "Houston", Hostname: "us-hou.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{162, 218, 228, 194}, {162, 218, 228, 196}, {66, 187, 75, 122}, {162, 218, 229, 106}}}, + {Country: "United States", City: "Las Vegas", Hostname: "us-lv.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{185, 242, 5, 18}, {185, 242, 5, 22}}}, + {Country: "United States", City: "Los Angeles", Hostname: "us-la.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{64, 31, 33, 154}, {192, 184, 48, 10}, {23, 83, 37, 209}, {23, 83, 37, 213}, {45, 136, 131, 40}, {69, 162, 99, 70}}}, + {Country: "United States", City: "Miami", Hostname: "us-mia.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{186, 233, 185, 29}, {186, 233, 185, 79}, {186, 233, 185, 80}, {186, 233, 184, 187}, {186, 233, 184, 188}, {186, 233, 184, 37}, {186, 233, 184, 31}}}, + {Country: "United States", City: "New York", Hostname: "us-ny-free.vpnunlimitedapp.com", Free: true, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{64, 94, 215, 66}, {64, 94, 215, 162}, {64, 94, 215, 170}}}, + {Country: "United States", City: "New York", Hostname: "us-ny.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{64, 42, 178, 202}, {23, 105, 134, 162}, {64, 42, 178, 226}, {23, 237, 58, 112}, {23, 108, 31, 122}}}, + {Country: "United States", City: "Saint Louis", Hostname: "us-sl.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{69, 64, 58, 110}, {69, 64, 58, 255}, {209, 239, 123, 77}, {209, 239, 123, 107}}}, + {Country: "United States", City: "Salt Lake City", Hostname: "us-slc.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{209, 95, 53, 223}, {209, 95, 53, 225}}}, + {Country: "United States", City: "San Francisco", Hostname: "us-sf.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{209, 58, 139, 34}, {209, 58, 135, 72}, {209, 58, 130, 210}, {209, 58, 135, 106}, {209, 58, 135, 108}, {209, 58, 135, 74}, {209, 58, 139, 35}, {209, 58, 137, 94}, {172, 241, 251, 164}, {209, 58, 135, 120}}}, + {Country: "United States", City: "Seattle", Hostname: "us-sea.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{216, 244, 82, 50}, {23, 81, 209, 137}, {216, 244, 82, 210}}}, + {Country: "Vietnam", City: "", Hostname: "vn.vpnunlimitedapp.com", Free: false, Stream: false, TCP: false, UDP: true, IPs: []net.IP{{103, 9, 78, 84}, {146, 196, 67, 7}}}, + } +} diff --git a/internal/models/server.go b/internal/models/server.go index e3d36bdd..cb88bed6 100644 --- a/internal/models/server.go +++ b/internal/models/server.go @@ -188,6 +188,22 @@ func (s *TorguardServer) String() string { s.Country, s.City, s.Hostname, s.TCP, s.UDP, goStringifyIPs(s.IPs)) } +type VPNUnlimitedServer struct { + Country string `json:"country"` + City string `json:"city"` + Hostname string `json:"hostname"` + Free bool `json:"free"` + Stream bool `json:"stream"` + TCP bool `json:"tcp"` + UDP bool `json:"udp"` + IPs []net.IP `json:"ips"` +} + +func (s *VPNUnlimitedServer) String() string { + return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, Free: %t, Stream: %t, TCP: %t, UDP: %t, IPs: %s}", + s.Country, s.City, s.Hostname, s.Free, s.Stream, s.TCP, s.UDP, goStringifyIPs(s.IPs)) +} + type VyprvpnServer struct { Region string `json:"region"` Hostname string `json:"hostname"` diff --git a/internal/models/servers.go b/internal/models/servers.go index e604e71e..63431d7c 100644 --- a/internal/models/servers.go +++ b/internal/models/servers.go @@ -1,22 +1,23 @@ package models type AllServers struct { - Version uint16 `json:"version"` - Cyberghost CyberghostServers `json:"cyberghost"` - Fastestvpn FastestvpnServers `json:"fastestvpn"` - HideMyAss HideMyAssServers `json:"hidemyass"` - Ivpn IvpnServers `json:"ivpn"` - Mullvad MullvadServers `json:"mullvad"` - Nordvpn NordvpnServers `json:"nordvpn"` - Privado PrivadoServers `json:"privado"` - Pia PiaServers `json:"pia"` - Privatevpn PrivatevpnServers `json:"privatevpn"` - Protonvpn ProtonvpnServers `json:"protonvpn"` - Purevpn PurevpnServers `json:"purevpn"` - Surfshark SurfsharkServers `json:"surfshark"` - Torguard TorguardServers `json:"torguard"` - Vyprvpn VyprvpnServers `json:"vyprvpn"` - Windscribe WindscribeServers `json:"windscribe"` + Version uint16 `json:"version"` + Cyberghost CyberghostServers `json:"cyberghost"` + Fastestvpn FastestvpnServers `json:"fastestvpn"` + HideMyAss HideMyAssServers `json:"hidemyass"` + Ivpn IvpnServers `json:"ivpn"` + Mullvad MullvadServers `json:"mullvad"` + Nordvpn NordvpnServers `json:"nordvpn"` + Privado PrivadoServers `json:"privado"` + Pia PiaServers `json:"pia"` + Privatevpn PrivatevpnServers `json:"privatevpn"` + Protonvpn ProtonvpnServers `json:"protonvpn"` + Purevpn PurevpnServers `json:"purevpn"` + Surfshark SurfsharkServers `json:"surfshark"` + Torguard TorguardServers `json:"torguard"` + VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"` + Vyprvpn VyprvpnServers `json:"vyprvpn"` + Windscribe WindscribeServers `json:"windscribe"` } func (a *AllServers) Count() int { @@ -33,6 +34,7 @@ func (a *AllServers) Count() int { len(a.Purevpn.Servers) + len(a.Surfshark.Servers) + len(a.Torguard.Servers) + + len(a.VPNUnlimited.Servers) + len(a.Vyprvpn.Servers) + len(a.Windscribe.Servers) } @@ -102,6 +104,11 @@ type TorguardServers struct { Timestamp int64 `json:"timestamp"` Servers []TorguardServer `json:"servers"` } +type VPNUnlimitedServers struct { + Version uint16 `json:"version"` + Timestamp int64 `json:"timestamp"` + Servers []VPNUnlimitedServer `json:"servers"` +} type VyprvpnServers struct { Version uint16 `json:"version"` Timestamp int64 `json:"timestamp"` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index fe6bdc22..0a093552 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -25,6 +25,7 @@ import ( "github.com/qdm12/gluetun/internal/provider/purevpn" "github.com/qdm12/gluetun/internal/provider/surfshark" "github.com/qdm12/gluetun/internal/provider/torguard" + "github.com/qdm12/gluetun/internal/provider/vpnunlimited" "github.com/qdm12/gluetun/internal/provider/vyprvpn" "github.com/qdm12/gluetun/internal/provider/windscribe" "github.com/qdm12/golibs/logging" @@ -69,6 +70,8 @@ func New(provider string, allServers models.AllServers, timeNow func() time.Time return surfshark.New(allServers.Surfshark.Servers, randSource) case constants.Torguard: return torguard.New(allServers.Torguard.Servers, randSource) + case constants.VPNUnlimited: + return vpnunlimited.New(allServers.VPNUnlimited.Servers, randSource) case constants.Vyprvpn: return vyprvpn.New(allServers.Vyprvpn.Servers, randSource) case constants.Windscribe: diff --git a/internal/provider/vpnunlimited/connection.go b/internal/provider/vpnunlimited/connection.go new file mode 100644 index 00000000..567a4d85 --- /dev/null +++ b/internal/provider/vpnunlimited/connection.go @@ -0,0 +1,44 @@ +package vpnunlimited + +import ( + "errors" + + "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" +) + +var ErrProtocolUnsupported = errors.New("network protocol is not supported") + +func (p *Provider) GetOpenVPNConnection(selection configuration.ServerSelection) ( + connection models.OpenVPNConnection, err error) { + const port = 1194 + const protocol = constants.UDP + if selection.TCP { + return connection, ErrProtocolUnsupported + } + + servers, err := p.filterServers(selection) + if err != nil { + return connection, err + } + + var connections []models.OpenVPNConnection + for _, server := range servers { + for _, IP := range server.IPs { + connection := models.OpenVPNConnection{ + IP: IP, + Port: port, + Protocol: protocol, + } + connections = append(connections, connection) + } + } + + if selection.TargetIP != nil { + return utils.GetTargetIPConnection(connections, selection.TargetIP) + } + + return utils.PickRandomConnection(connections, p.randSource), nil +} diff --git a/internal/provider/vpnunlimited/filter.go b/internal/provider/vpnunlimited/filter.go new file mode 100644 index 00000000..ac724522 --- /dev/null +++ b/internal/provider/vpnunlimited/filter.go @@ -0,0 +1,31 @@ +package vpnunlimited + +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.VPNUnlimitedServer, 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), + selection.FreeOnly && !server.Free, + selection.StreamOnly && !server.Stream, + selection.TCP && !server.TCP, + !selection.TCP && !server.UDP: + default: + servers = append(servers, server) + } + } + + if len(servers) == 0 { + return nil, utils.NoServerFoundError(selection) + } + + return servers, nil +} diff --git a/internal/provider/vpnunlimited/openvpnconf.go b/internal/provider/vpnunlimited/openvpnconf.go new file mode 100644 index 00000000..24afcb2e --- /dev/null +++ b/internal/provider/vpnunlimited/openvpnconf.go @@ -0,0 +1,69 @@ +package vpnunlimited + +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.OpenVPNConnection, + username string, settings configuration.OpenVPN) (lines []string) { + lines = []string{ + "client", + "dev tun", + "nobind", + "persist-key", + "tls-exit", + "remote-cert-tls server", + + // VPNUnlimited specific + "reneg-sec 0", + "ping 5", + "ping-exit 30", + "comp-lzo no", + "route-metric 1", + + // Added constant values + "auth-nocache", + "mute-replay-warnings", + "pull-filter ignore \"auth-token\"", // prevent auth failed loops + "auth-retry nointeract", + "suppress-timestamps", + + // Modified variables + "verb " + strconv.Itoa(settings.Verbosity), + // "auth-user-pass " + constants.OpenVPNAuthConf, + connection.ProtoLine(), + connection.RemoteLine(), + } + + if settings.Cipher != "" { + lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) + } + + if settings.Auth != "" { + lines = append(lines, "auth "+settings.Auth) + } + + if settings.MSSFix > 0 { + lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix))) + } + + if !settings.Root { + lines = append(lines, "user "+username) + } + + lines = append(lines, utils.WrapOpenvpnCA( + constants.VPNUnlimitedCertificateAuthority)...) + lines = append(lines, utils.WrapOpenvpnCert( + settings.Provider.ExtraConfigOptions.ClientCertificate)...) + lines = append(lines, utils.WrapOpenvpnKey( + settings.Provider.ExtraConfigOptions.ClientKey)...) + + lines = append(lines, "") + + return lines +} diff --git a/internal/provider/vpnunlimited/portforward.go b/internal/provider/vpnunlimited/portforward.go new file mode 100644 index 00000000..db5ee010 --- /dev/null +++ b/internal/provider/vpnunlimited/portforward.go @@ -0,0 +1,17 @@ +package vpnunlimited + +import ( + "context" + "net" + "net/http" + + "github.com/qdm12/gluetun/internal/firewall" + "github.com/qdm12/golibs/logging" + "github.com/qdm12/golibs/os" +) + +func (p *Provider) 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 VPN Unlimited") +} diff --git a/internal/provider/vpnunlimited/provider.go b/internal/provider/vpnunlimited/provider.go new file mode 100644 index 00000000..7ad0a9ae --- /dev/null +++ b/internal/provider/vpnunlimited/provider.go @@ -0,0 +1,19 @@ +package vpnunlimited + +import ( + "math/rand" + + "github.com/qdm12/gluetun/internal/models" +) + +type Provider struct { + servers []models.VPNUnlimitedServer + randSource rand.Source +} + +func New(servers []models.VPNUnlimitedServer, randSource rand.Source) *Provider { + return &Provider{ + servers: servers, + randSource: randSource, + } +} diff --git a/internal/storage/merge.go b/internal/storage/merge.go index 34e84c16..178cf47e 100644 --- a/internal/storage/merge.go +++ b/internal/storage/merge.go @@ -4,6 +4,7 @@ import ( "strconv" "time" + "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" ) @@ -31,22 +32,23 @@ func (s *storage) logTimeDiff(provider string, persistedUnix, hardcodedUnix int6 func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.AllServers { 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), - Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn), - Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), - Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), - Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), - Pia: s.mergePIA(hardcoded.Pia, persisted.Pia), - Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn), - Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn), - Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn), - Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark), - Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard), - Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn), - Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe), + Version: hardcoded.Version, + Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost), + Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn), + HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss), + Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn), + Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), + Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), + Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), + Pia: s.mergePIA(hardcoded.Pia, persisted.Pia), + Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn), + Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn), + Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn), + Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark), + Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard), + VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited), + Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn), + Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe), } } @@ -234,6 +236,20 @@ func (s *storage) mergeTorguard(hardcoded, persisted models.TorguardServers) mod return persisted } +func (s *storage) mergeVPNUnlimited(hardcoded, persisted models.VPNUnlimitedServers) models.VPNUnlimitedServers { + if persisted.Timestamp <= hardcoded.Timestamp { + return hardcoded + } + versionDiff := hardcoded.Version - persisted.Version + if versionDiff > 0 { + s.logVersionDiff(constants.VPNUnlimited, versionDiff) + return hardcoded + } + + s.logTimeDiff(constants.VPNUnlimited, persisted.Timestamp, hardcoded.Timestamp) + return persisted +} + func (s *storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) models.VyprvpnServers { if persisted.Timestamp <= hardcoded.Timestamp { return hardcoded diff --git a/internal/storage/sync.go b/internal/storage/sync.go index a3a7e4ae..0322fea2 100644 --- a/internal/storage/sync.go +++ b/internal/storage/sync.go @@ -31,6 +31,7 @@ func countServers(allServers models.AllServers) int { len(allServers.Purevpn.Servers) + len(allServers.Surfshark.Servers) + len(allServers.Torguard.Servers) + + len(allServers.VPNUnlimited.Servers) + len(allServers.Vyprvpn.Servers) + len(allServers.Windscribe.Servers) } diff --git a/internal/updater/providers.go b/internal/updater/providers.go index 4cbfbd99..02936b57 100644 --- a/internal/updater/providers.go +++ b/internal/updater/providers.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/updater/providers/cyberghost" "github.com/qdm12/gluetun/internal/updater/providers/fastestvpn" "github.com/qdm12/gluetun/internal/updater/providers/hidemyass" @@ -17,6 +18,7 @@ import ( "github.com/qdm12/gluetun/internal/updater/providers/purevpn" "github.com/qdm12/gluetun/internal/updater/providers/surfshark" "github.com/qdm12/gluetun/internal/updater/providers/torguard" + "github.com/qdm12/gluetun/internal/updater/providers/vpnunlimited" "github.com/qdm12/gluetun/internal/updater/providers/vyprvpn" "github.com/qdm12/gluetun/internal/updater/providers/windscribe" ) @@ -261,6 +263,26 @@ func (u *updater) updateTorguard(ctx context.Context) (err error) { return nil } +func (u *updater) updateVPNUnlimited(ctx context.Context) (err error) { + minServers := getMinServers(len(u.servers.VPNUnlimited.Servers)) + servers, warnings, err := vpnunlimited.GetServers( + ctx, u.unzipper, u.presolver, minServers) + if u.options.CLI { + for _, warning := range warnings { + u.logger.Warn(constants.VPNUnlimited + ": " + warning) + } + } + if err != nil { + return err + } + if u.options.Stdout { + u.println(vpnunlimited.Stringify(servers)) + } + u.servers.VPNUnlimited.Timestamp = u.timeNow().Unix() + u.servers.VPNUnlimited.Servers = servers + return nil +} + func (u *updater) updateVyprvpn(ctx context.Context) (err error) { minServers := getMinServers(len(u.servers.Vyprvpn.Servers)) servers, warnings, err := vyprvpn.GetServers( diff --git a/internal/updater/providers/vpnunlimited/constants.go b/internal/updater/providers/vpnunlimited/constants.go new file mode 100644 index 00000000..10ed9903 --- /dev/null +++ b/internal/updater/providers/vpnunlimited/constants.go @@ -0,0 +1,160 @@ +package vpnunlimited + +import ( + "strings" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/models" +) + +func getHostToServer() (hts hostToServer, warnings []string) { + shortHTS := map[string]models.VPNUnlimitedServer{ + "ae": {}, + "ar": {}, + "at": {}, + "au-syd": { + City: "Sydney", + }, + "ba": {}, + "be": {}, + "bg": {}, + "br": {}, + "by": {}, + "ca-tr": { + City: "Toronto", + }, + "ca-vn": { + City: "Vancouver", + }, + "ca": {}, + "ch": {}, + "cr": {}, + "cy": {}, + "cz": {}, + "de-dus": { + City: "Düsseldorf", + }, + "de": {}, + "dk": {}, + "ee": {}, + "es": {}, + "fi": {}, + "fr-rbx": { + City: "Roubaix", + }, + "fr": {}, + "gr": {}, + "hr": {}, + "hu": {}, + "ie-dub": { + City: "Dublin", + }, + "il": {}, + "im": {}, + "in-ka": { + City: "Karnataka", + }, + "in": {}, + "is": {}, + "it-mil": { + City: "Milan", + }, + "jp": {}, + "kr": {}, + "lt": {}, + "lu": {}, + "lv": {}, + "ly": {}, + "md": {}, + "mx": {}, + "mys": {}, + "nl": {}, + "no": {}, + "nz": {}, + "om": {}, + "pl": {}, + "pt": {}, + "ro": {}, + "rs": {}, + "se": {}, + "sg-free": { + Free: true, + }, + "sg": {}, + "si": {}, + "sk": {}, + "th": {}, + "tr": {}, + "uk-cv": { + City: "London", + }, + "uk-lon": { + City: "London", + }, + "uk": {}, + "us-chi": { + City: "Chicago", + }, + "us-dal": { + City: "Dallas", + }, + "us-den": { + City: "Denver", + }, + "us-hou": { + City: "Houston", + }, + "us-la": { + City: "Los Angeles", + }, + "us-lv": { + City: "Las Vegas", + }, + "us-mia": { + City: "Miami", + }, + "us-ny-free": { + City: "New York", + Free: true, + }, + "us-ny": { + City: "New York", + }, + "us-sea": { + City: "Seattle", + }, + "us-sf": { + City: "San Francisco", + }, + "us-sl": { + City: "Saint Louis", + }, + "us-slc": { + City: "Salt Lake City", + }, + "us-stream": { + Stream: true, + }, + "us": {}, + "vn": {}, + "za": {}, + } + + hts = make(hostToServer, len(shortHTS)) + + countryCodesMap := constants.CountryCodes() + for shortHost, server := range shortHTS { + server.UDP = true + server.Hostname = shortHost + ".vpnunlimitedapp.com" + countryCode := strings.Split(shortHost, "-")[0] + country, ok := countryCodesMap[countryCode] + if !ok { + warnings = append(warnings, "country code not found: "+countryCode) + continue + } + server.Country = country + hts[server.Hostname] = server + } + + return hts, warnings +} diff --git a/internal/updater/providers/vpnunlimited/hosttoserver.go b/internal/updater/providers/vpnunlimited/hosttoserver.go new file mode 100644 index 00000000..7a179451 --- /dev/null +++ b/internal/updater/providers/vpnunlimited/hosttoserver.go @@ -0,0 +1,38 @@ +package vpnunlimited + +import ( + "net" + + "github.com/qdm12/gluetun/internal/models" +) + +type hostToServer map[string]models.VPNUnlimitedServer + +func (hts hostToServer) toHostsSlice() (hosts []string) { + hosts = make([]string, 0, len(hts)) + for host := range hts { + hosts = append(hosts, host) + } + return hosts +} + +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { + for host, IPs := range hostToIPs { + server := hts[host] + server.IPs = IPs + hts[host] = server + } + for host, server := range hts { + if len(server.IPs) == 0 { + delete(hts, host) + } + } +} + +func (hts hostToServer) toServersSlice() (servers []models.VPNUnlimitedServer) { + servers = make([]models.VPNUnlimitedServer, 0, len(hts)) + for _, server := range hts { + servers = append(servers, server) + } + return servers +} diff --git a/internal/updater/providers/vpnunlimited/resolve.go b/internal/updater/providers/vpnunlimited/resolve.go new file mode 100644 index 00000000..487cf222 --- /dev/null +++ b/internal/updater/providers/vpnunlimited/resolve.go @@ -0,0 +1,32 @@ +package vpnunlimited + +import ( + "context" + "net" + "time" + + "github.com/qdm12/gluetun/internal/updater/resolver" +) + +func resolveHosts(ctx context.Context, presolver resolver.Parallel, + hosts []string, minServers int) (hostToIPs map[string][]net.IP, + warnings []string, err error) { + const ( + maxFailRatio = 0.1 + maxDuration = 20 * time.Second + betweenDuration = time.Second + maxNoNew = 2 + maxFails = 2 + ) + settings := resolver.ParallelSettings{ + MaxFailRatio: maxFailRatio, + MinFound: minServers, + Repeat: resolver.RepeatSettings{ + MaxDuration: maxDuration, + BetweenDuration: betweenDuration, + MaxNoNew: maxNoNew, + MaxFails: maxFails, + }, + } + return presolver.Resolve(ctx, hosts, settings) +} diff --git a/internal/updater/providers/vpnunlimited/servers.go b/internal/updater/providers/vpnunlimited/servers.go new file mode 100644 index 00000000..d46880eb --- /dev/null +++ b/internal/updater/providers/vpnunlimited/servers.go @@ -0,0 +1,42 @@ +// Package vpnunlimited contains code to obtain the server information +// for the VPNUnlimited provider. +package vpnunlimited + +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.VPNUnlimitedServer, warnings []string, err error) { + // Hardcoded data from a user provided ZIP file since it's behind a login wall + hts, warnings := getHostToServer() + + hosts := hts.toHostsSlice() + hostToIPs, newWarnings, err := resolveHosts(ctx, presolver, hosts, minServers) + warnings = append(warnings, newWarnings...) + if err != nil { + return nil, warnings, err + } + + hts.adaptWithIPs(hostToIPs) + + servers = hts.toServersSlice() + + 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 +} diff --git a/internal/updater/providers/vpnunlimited/sort.go b/internal/updater/providers/vpnunlimited/sort.go new file mode 100644 index 00000000..86701e34 --- /dev/null +++ b/internal/updater/providers/vpnunlimited/sort.go @@ -0,0 +1,19 @@ +package vpnunlimited + +import ( + "sort" + + "github.com/qdm12/gluetun/internal/models" +) + +func sortServers(servers []models.VPNUnlimitedServer) { + 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 + }) +} diff --git a/internal/updater/providers/vpnunlimited/string.go b/internal/updater/providers/vpnunlimited/string.go new file mode 100644 index 00000000..ffe0eeea --- /dev/null +++ b/internal/updater/providers/vpnunlimited/string.go @@ -0,0 +1,14 @@ +package vpnunlimited + +import "github.com/qdm12/gluetun/internal/models" + +func Stringify(servers []models.VPNUnlimitedServer) (s string) { + s = "func VPNUnlimitedServers() []models.VPNUnlimitedServer {\n" + s += " return []models.VPNUnlimitedServer{\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 38f7e437..47cb9c3c 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -8,6 +8,7 @@ import ( "time" "github.com/qdm12/gluetun/internal/configuration" + "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/updater/resolver" "github.com/qdm12/gluetun/internal/updater/unzip" @@ -186,6 +187,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe } } + if u.options.VPNUnlimited { + u.logger.Info("updating " + constants.VPNUnlimited + " servers...") + if err := u.updateVPNUnlimited(ctx); err != nil { + if ctxErr := ctx.Err(); ctxErr != nil { + return allServers, ctxErr + } + u.logger.Error(err) + } + } + if u.options.Vyprvpn { u.logger.Info("updating Vyprvpn servers...") if err := u.updateVyprvpn(ctx); err != nil {