VPN Unlimited support (#499)

- Fixes #420 
- Revert to docker/build-push-action@v2.4.0
This commit is contained in:
Quentin McGaw
2021-06-20 09:18:03 -07:00
committed by GitHub
parent 400affe429
commit d81d4bbda3
40 changed files with 1245 additions and 365 deletions

3
.github/labels.yml vendored
View File

@@ -53,6 +53,9 @@
- name: ":cloud: Torguard"
color: "cfe8d4"
description: ""
- name: ":cloud: VPNUnlimited"
color: "cfe8d4"
description: ""
- name: ":cloud: Vyprvpn"
color: "cfe8d4"
description: ""

View File

@@ -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: |

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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": "",

View File

@@ -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":

View File

@@ -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,

View File

@@ -15,9 +15,12 @@ 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
// 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
@@ -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
}

View File

@@ -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
}

View File

@@ -23,6 +23,7 @@ type Updater struct {
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
@@ -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

View File

@@ -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
}

View File

@@ -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)
})
}
}

View File

@@ -136,6 +136,7 @@ func CountryCodes() map[string]string {
"mg": "Madagascar",
"mw": "Malawi",
"my": "Malaysia",
"mys": "Kuala Lumpur",
"mv": "Maldives",
"ml": "Mali",
"mt": "Malta",

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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}}},
}
}

View File

@@ -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"`

View File

@@ -15,6 +15,7 @@ type AllServers struct {
Purevpn PurevpnServers `json:"purevpn"`
Surfshark SurfsharkServers `json:"surfshark"`
Torguard TorguardServers `json:"torguard"`
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
Vyprvpn VyprvpnServers `json:"vyprvpn"`
Windscribe WindscribeServers `json:"windscribe"`
}
@@ -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"`

View File

@@ -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:

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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,
}
}

View File

@@ -4,6 +4,7 @@ import (
"strconv"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
)
@@ -45,6 +46,7 @@ func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
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

View File

@@ -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)
}

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
})
}

View File

@@ -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
}

View File

@@ -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 {