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" - name: ":cloud: Torguard"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: VPNUnlimited"
color: "cfe8d4"
description: ""
- name: ":cloud: Vyprvpn" - name: ":cloud: Vyprvpn"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -86,7 +86,7 @@ jobs:
fi fi
- name: Build and push final image - name: Build and push final image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2.4.0
with: with:
platforms: ${{ steps.vars.outputs.platforms }} platforms: ${{ steps.vars.outputs.platforms }}
build-args: | build-args: |

View File

@@ -2,7 +2,7 @@
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN, *Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
HideMyAss, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN, 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* using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: **ANNOUNCEMENT**:
@@ -39,7 +39,7 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
## Features ## Features
- Based on Alpine 3.13 for a small Docker image of 54MB - 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 - Supports Openvpn only for now
- DNS over TLS baked in with service provider(s) of your choice - DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours

View File

@@ -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.Purevpn, "purevpn", false, "Update Purevpn servers")
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers") flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard 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.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers") flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {

View File

@@ -1,10 +1,6 @@
package configuration package configuration
import ( import (
"encoding/pem"
"errors"
"strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params" "github.com/qdm12/golibs/params"
) )
@@ -44,12 +40,12 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
return err return err
} }
settings.ExtraConfigOptions.ClientKey, err = readCyberghostClientKey(r) settings.ExtraConfigOptions.ClientKey, err = readClientKey(r)
if err != nil { if err != nil {
return err return err
} }
settings.ExtraConfigOptions.ClientCertificate, err = readCyberghostClientCertificate(r) settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r)
if err != nil { if err != nil {
return err return err
} }
@@ -72,49 +68,3 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
return nil 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{ vpnsp, err := r.env.Inside("VPNSP", []string{
"cyberghost", "fastestvpn", "hidemyass", "ivpn", "mullvad", "nordvpn", "cyberghost", "fastestvpn", "hidemyass", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn", "privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"}, "purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"},
params.Default("private internet access")) params.Default("private internet access"))
if err != nil { if err != nil {
return err return err
@@ -86,12 +86,13 @@ func (settings *OpenVPN) read(r reader) (err error) {
return err return err
} }
customConfig := settings.Config != "" customConfig := settings.Config != ""
credentialsRequired := true
if customConfig { if customConfig {
credentialsRequired = false
settings.Provider.Name = "" settings.Provider.Name = ""
} }
credentialsRequired := !customConfig && settings.Provider.Name != constants.VPNUnlimited
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"}) settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
if err != nil { if err != nil {
return err return err
@@ -139,7 +140,10 @@ func (settings *OpenVPN) read(r reader) (err error) {
return err return err
} }
settings.MSSFix = uint16(mssFix) settings.MSSFix = uint16(mssFix)
return settings.readProvider(r)
}
func (settings *OpenVPN) readProvider(r reader) error {
var readProvider func(r reader) error var readProvider func(r reader) error
switch settings.Provider.Name { switch settings.Provider.Name {
case "": // custom config case "": // custom config
@@ -170,6 +174,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
readProvider = settings.Provider.readSurfshark readProvider = settings.Provider.readSurfshark
case constants.Torguard: case constants.Torguard:
readProvider = settings.Provider.readTorguard readProvider = settings.Provider.readTorguard
case constants.VPNUnlimited:
readProvider = settings.Provider.readVPNUnlimited
case constants.Vyprvpn: case constants.Vyprvpn:
readProvider = settings.Provider.readVyprvpn readProvider = settings.Provider.readVyprvpn
case constants.Windscribe: case constants.Windscribe:
@@ -177,6 +183,5 @@ func (settings *OpenVPN) read(r reader) (err error) {
default: default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name) return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
} }
return readProvider(r) return readProvider(r)
} }

View File

@@ -41,7 +41,8 @@ func Test_OpenVPN_JSON(t *testing.T) {
"custom_port": 0, "custom_port": 0,
"numbers": null, "numbers": null,
"encryption_preset": "", "encryption_preset": "",
"free_only": false "free_only": false,
"stream_only": false
}, },
"extra_config": { "extra_config": {
"encryption_preset": "", "encryption_preset": "",

View File

@@ -56,6 +56,8 @@ func (settings *Provider) lines() (lines []string) {
providerLines = settings.surfsharkLines() providerLines = settings.surfsharkLines()
case "torguard": case "torguard":
providerLines = settings.torguardLines() providerLines = settings.torguardLines()
case strings.ToLower(constants.VPNUnlimited):
providerLines = settings.vpnUnlimitedLines()
case "vyprvpn": case "vyprvpn":
providerLines = settings.vyprvpnLines() providerLines = settings.vyprvpnLines()
case "windscribe": case "windscribe":

View File

@@ -249,6 +249,31 @@ func Test_Provider_lines(t *testing.T) {
" |--Hostnames: e", " |--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": { "vyprvpn": {
settings: Provider{ settings: Provider{
Name: constants.Vyprvpn, Name: constants.Vyprvpn,

View File

@@ -15,10 +15,13 @@ type ServerSelection struct { //nolint:maligned
// Cyberghost // Cyberghost
Group string `json:"group"` Group string `json:"group"`
Countries []string `json:"countries"` // Fastestvpn, HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN // Fastestvpn, HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
Cities []string `json:"cities"` // HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, Windscribe Countries []string `json:"countries"`
Hostnames []string `json:"hostnames"` // Fastestvpn, HideMyAss, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn // HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, Windscribe
Names []string `json:"names"` // Protonvpn Cities []string `json:"cities"`
// Fastestvpn, HideMyAss, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited
Hostnames []string `json:"hostnames"`
Names []string `json:"names"` // Protonvpn
// Mullvad // Mullvad
ISPs []string `json:"isps"` ISPs []string `json:"isps"`
@@ -35,11 +38,14 @@ type ServerSelection struct { //nolint:maligned
// ProtonVPN // ProtonVPN
FreeOnly bool `json:"free_only"` FreeOnly bool `json:"free_only"`
// VPNUnlimited
StreamOnly bool `json:"stream_only"`
} }
type ExtraConfigOptions struct { type ExtraConfigOptions struct {
ClientCertificate string `json:"-"` // Cyberghost ClientCertificate string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
EncryptionPreset string `json:"encryption_preset"` // PIA EncryptionPreset string `json:"encryption_preset"` // PIA
OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad
} }

View File

@@ -43,7 +43,7 @@ func (settings *Provider) readTorguard(r reader) (err error) {
return err 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 { if err != nil {
return err return err
} }

View File

@@ -8,23 +8,24 @@ import (
) )
type Updater struct { type Updater struct {
Period time.Duration `json:"period"` Period time.Duration `json:"period"`
DNSAddress string `json:"dns_address"` DNSAddress string `json:"dns_address"`
Cyberghost bool `json:"cyberghost"` Cyberghost bool `json:"cyberghost"`
Fastestvpn bool `json:"fastestvpn"` Fastestvpn bool `json:"fastestvpn"`
HideMyAss bool `json:"hidemyass"` HideMyAss bool `json:"hidemyass"`
Ivpn bool `json:"ivpn"` Ivpn bool `json:"ivpn"`
Mullvad bool `json:"mullvad"` Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"` Nordvpn bool `json:"nordvpn"`
PIA bool `json:"pia"` PIA bool `json:"pia"`
Privado bool `json:"privado"` Privado bool `json:"privado"`
Privatevpn bool `json:"privatevpn"` Privatevpn bool `json:"privatevpn"`
Protonvpn bool `json:"protonvpn"` Protonvpn bool `json:"protonvpn"`
Purevpn bool `json:"purevpn"` Purevpn bool `json:"purevpn"`
Surfshark bool `json:"surfshark"` Surfshark bool `json:"surfshark"`
Torguard bool `json:"torguard"` Torguard bool `json:"torguard"`
Vyprvpn bool `json:"vyprvpn"` VPNUnlimited bool `json:"vpnunlimited"`
Windscribe bool `json:"windscribe"` Vyprvpn bool `json:"vyprvpn"`
Windscribe bool `json:"windscribe"`
// The two below should be used in CLI mode only // The two below should be used in CLI mode only
Stdout bool `json:"-"` // in order to update constants file (maintainer side) Stdout bool `json:"-"` // in order to update constants file (maintainer side)
CLI bool `json:"-"` CLI bool `json:"-"`
@@ -60,6 +61,7 @@ func (settings *Updater) read(r reader) (err error) {
settings.Purevpn = true settings.Purevpn = true
settings.Surfshark = true settings.Surfshark = true
settings.Torguard = true settings.Torguard = true
settings.VPNUnlimited = true
settings.Vyprvpn = true settings.Vyprvpn = true
settings.Windscribe = true settings.Windscribe = true
settings.Stdout = false 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

@@ -2,254 +2,255 @@ package constants
func CountryCodes() map[string]string { func CountryCodes() map[string]string {
return map[string]string{ return map[string]string{
"af": "Afghanistan", "af": "Afghanistan",
"ax": "Aland Islands", "ax": "Aland Islands",
"al": "Albania", "al": "Albania",
"dz": "Algeria", "dz": "Algeria",
"as": "American Samoa", "as": "American Samoa",
"ad": "Andorra", "ad": "Andorra",
"ao": "Angola", "ao": "Angola",
"ai": "Anguilla", "ai": "Anguilla",
"aq": "Antarctica", "aq": "Antarctica",
"ag": "Antigua and Barbuda", "ag": "Antigua and Barbuda",
"ar": "Argentina", "ar": "Argentina",
"am": "Armenia", "am": "Armenia",
"aw": "Aruba", "aw": "Aruba",
"au": "Australia", "au": "Australia",
"at": "Austria", "at": "Austria",
"az": "Azerbaijan", "az": "Azerbaijan",
"bs": "Bahamas", "bs": "Bahamas",
"bh": "Bahrain", "bh": "Bahrain",
"bd": "Bangladesh", "bd": "Bangladesh",
"bb": "Barbados", "bb": "Barbados",
"by": "Belarus", "by": "Belarus",
"be": "Belgium", "be": "Belgium",
"bz": "Belize", "bz": "Belize",
"bj": "Benin", "bj": "Benin",
"bm": "Bermuda", "bm": "Bermuda",
"bt": "Bhutan", "bt": "Bhutan",
"bo": "Bolivia", "bo": "Bolivia",
"bq": "Bonaire", "bq": "Bonaire",
"ba": "Bosnia and Herzegovina", "ba": "Bosnia and Herzegovina",
"bw": "Botswana", "bw": "Botswana",
"bv": "Bouvet Island", "bv": "Bouvet Island",
"br": "Brazil", "br": "Brazil",
"io": "British Indian Ocean Territory", "io": "British Indian Ocean Territory",
"vg": "British Virgin Islands", "vg": "British Virgin Islands",
"bn": "Brunei Darussalam", "bn": "Brunei Darussalam",
"bg": "Bulgaria", "bg": "Bulgaria",
"bf": "Burkina Faso", "bf": "Burkina Faso",
"bi": "Burundi", "bi": "Burundi",
"kh": "Cambodia", "kh": "Cambodia",
"cm": "Cameroon", "cm": "Cameroon",
"ca": "Canada", "ca": "Canada",
"cv": "Cape Verde", "cv": "Cape Verde",
"ky": "Cayman Islands", "ky": "Cayman Islands",
"cf": "Central African Republic", "cf": "Central African Republic",
"td": "Chad", "td": "Chad",
"cl": "Chile", "cl": "Chile",
"cn": "China", "cn": "China",
"cx": "Christmas Island", "cx": "Christmas Island",
"cc": "Cocos Islands", "cc": "Cocos Islands",
"co": "Colombia", "co": "Colombia",
"km": "Comoros", "km": "Comoros",
"cg": "Congo", "cg": "Congo",
"ck": "Cook Islands", "ck": "Cook Islands",
"cr": "Costa Rica", "cr": "Costa Rica",
"ci": "Cote d'Ivoire", "ci": "Cote d'Ivoire",
"hr": "Croatia", "hr": "Croatia",
"cu": "Cuba", "cu": "Cuba",
"cw": "Curacao", "cw": "Curacao",
"cy": "Cyprus", "cy": "Cyprus",
"cz": "Czech Republic", "cz": "Czech Republic",
"cd": "Democratic Republic of the Congo", "cd": "Democratic Republic of the Congo",
"dk": "Denmark", "dk": "Denmark",
"dj": "Djibouti", "dj": "Djibouti",
"dm": "Dominica", "dm": "Dominica",
"do": "Dominican Republic", "do": "Dominican Republic",
"ec": "Ecuador", "ec": "Ecuador",
"eg": "Egypt", "eg": "Egypt",
"sv": "El Salvador", "sv": "El Salvador",
"gq": "Equatorial Guinea", "gq": "Equatorial Guinea",
"er": "Eritrea", "er": "Eritrea",
"ee": "Estonia", "ee": "Estonia",
"et": "Ethiopia", "et": "Ethiopia",
"fk": "Falkland Islands", "fk": "Falkland Islands",
"fo": "Faroe Islands", "fo": "Faroe Islands",
"fj": "Fiji", "fj": "Fiji",
"fi": "Finland", "fi": "Finland",
"fr": "France", "fr": "France",
"gf": "French Guiana", "gf": "French Guiana",
"pf": "French Polynesia", "pf": "French Polynesia",
"tf": "French Southern Territories", "tf": "French Southern Territories",
"ga": "Gabon", "ga": "Gabon",
"gm": "Gambia", "gm": "Gambia",
"ge": "Georgia", "ge": "Georgia",
"de": "Germany", "de": "Germany",
"gh": "Ghana", "gh": "Ghana",
"gi": "Gibraltar", "gi": "Gibraltar",
"gr": "Greece", "gr": "Greece",
"gl": "Greenland", "gl": "Greenland",
"gd": "Grenada", "gd": "Grenada",
"gp": "Guadeloupe", "gp": "Guadeloupe",
"gu": "Guam", "gu": "Guam",
"gt": "Guatemala", "gt": "Guatemala",
"gg": "Guernsey", "gg": "Guernsey",
"gw": "Guinea-Bissau", "gw": "Guinea-Bissau",
"gn": "Guinea", "gn": "Guinea",
"gy": "Guyana", "gy": "Guyana",
"ht": "Haiti", "ht": "Haiti",
"hm": "Heard Island and McDonald Islands", "hm": "Heard Island and McDonald Islands",
"hn": "Honduras", "hn": "Honduras",
"hk": "Hong Kong", "hk": "Hong Kong",
"hu": "Hungary", "hu": "Hungary",
"is": "Iceland", "is": "Iceland",
"in": "India", "in": "India",
"id": "Indonesia", "id": "Indonesia",
"ir": "Iran", "ir": "Iran",
"iq": "Iraq", "iq": "Iraq",
"ie": "Ireland", "ie": "Ireland",
"im": "Isle of Man", "im": "Isle of Man",
"il": "Israel", "il": "Israel",
"it": "Italy", "it": "Italy",
"jm": "Jamaica", "jm": "Jamaica",
"jp": "Japan", "jp": "Japan",
"je": "Jersey", "je": "Jersey",
"jo": "Jordan", "jo": "Jordan",
"kz": "Kazakhstan", "kz": "Kazakhstan",
"ke": "Kenya", "ke": "Kenya",
"ki": "Kiribati", "ki": "Kiribati",
"kr": "Korea", "kr": "Korea",
"kw": "Kuwait", "kw": "Kuwait",
"kg": "Kyrgyzstan", "kg": "Kyrgyzstan",
"la": "Lao People's Democratic Republic", "la": "Lao People's Democratic Republic",
"lv": "Latvia", "lv": "Latvia",
"lb": "Lebanon", "lb": "Lebanon",
"ls": "Lesotho", "ls": "Lesotho",
"lr": "Liberia", "lr": "Liberia",
"ly": "Libya", "ly": "Libya",
"li": "Liechtenstein", "li": "Liechtenstein",
"lt": "Lithuania", "lt": "Lithuania",
"lu": "Luxembourg", "lu": "Luxembourg",
"mo": "Macao", "mo": "Macao",
"mk": "Macedonia", "mk": "Macedonia",
"mg": "Madagascar", "mg": "Madagascar",
"mw": "Malawi", "mw": "Malawi",
"my": "Malaysia", "my": "Malaysia",
"mv": "Maldives", "mys": "Kuala Lumpur",
"ml": "Mali", "mv": "Maldives",
"mt": "Malta", "ml": "Mali",
"mh": "Marshall Islands", "mt": "Malta",
"mq": "Martinique", "mh": "Marshall Islands",
"mr": "Mauritania", "mq": "Martinique",
"mu": "Mauritius", "mr": "Mauritania",
"yt": "Mayotte", "mu": "Mauritius",
"mx": "Mexico", "yt": "Mayotte",
"fm": "Micronesia", "mx": "Mexico",
"md": "Moldova", "fm": "Micronesia",
"mc": "Monaco", "md": "Moldova",
"mn": "Mongolia", "mc": "Monaco",
"me": "Montenegro", "mn": "Mongolia",
"ms": "Montserrat", "me": "Montenegro",
"ma": "Morocco", "ms": "Montserrat",
"mz": "Mozambique", "ma": "Morocco",
"mm": "Myanmar", "mz": "Mozambique",
"na": "Namibia", "mm": "Myanmar",
"nr": "Nauru", "na": "Namibia",
"np": "Nepal", "nr": "Nauru",
"nl": "Netherlands", "np": "Nepal",
"nc": "New Caledonia", "nl": "Netherlands",
"nz": "New Zealand", "nc": "New Caledonia",
"ni": "Nicaragua", "nz": "New Zealand",
"ne": "Niger", "ni": "Nicaragua",
"ng": "Nigeria", "ne": "Niger",
"nu": "Niue", "ng": "Nigeria",
"nf": "Norfolk Island", "nu": "Niue",
"mp": "Northern Mariana Islands", "nf": "Norfolk Island",
"no": "Norway", "mp": "Northern Mariana Islands",
"om": "Oman", "no": "Norway",
"pk": "Pakistan", "om": "Oman",
"pw": "Palau", "pk": "Pakistan",
"ps": "Palestine, State of", "pw": "Palau",
"pa": "Panama", "ps": "Palestine, State of",
"pg": "Papua New Guinea", "pa": "Panama",
"py": "Paraguay", "pg": "Papua New Guinea",
"pe": "Peru", "py": "Paraguay",
"ph": "Philippines", "pe": "Peru",
"pn": "Pitcairn", "ph": "Philippines",
"pl": "Poland", "pn": "Pitcairn",
"pt": "Portugal", "pl": "Poland",
"pr": "Puerto Rico", "pt": "Portugal",
"qa": "Qatar", "pr": "Puerto Rico",
"re": "Reunion", "qa": "Qatar",
"ro": "Romania", "re": "Reunion",
"ru": "Russian Federation", "ro": "Romania",
"rw": "Rwanda", "ru": "Russian Federation",
"bl": "Saint Barthelemy", "rw": "Rwanda",
"sh": "Saint Helena", "bl": "Saint Barthelemy",
"kn": "Saint Kitts and Nevis", "sh": "Saint Helena",
"lc": "Saint Lucia", "kn": "Saint Kitts and Nevis",
"mf": "Saint Martin", "lc": "Saint Lucia",
"pm": "Saint Pierre and Miquelon", "mf": "Saint Martin",
"vc": "Saint Vincent and the Grenadines", "pm": "Saint Pierre and Miquelon",
"ws": "Samoa", "vc": "Saint Vincent and the Grenadines",
"sm": "San Marino", "ws": "Samoa",
"st": "Sao Tome and Principe", "sm": "San Marino",
"sa": "Saudi Arabia", "st": "Sao Tome and Principe",
"sn": "Senegal", "sa": "Saudi Arabia",
"rs": "Serbia", "sn": "Senegal",
"sc": "Seychelles", "rs": "Serbia",
"sl": "Sierra Leone", "sc": "Seychelles",
"sg": "Singapore", "sl": "Sierra Leone",
"sx": "Sint Maarten", "sg": "Singapore",
"sk": "Slovakia", "sx": "Sint Maarten",
"si": "Slovenia", "sk": "Slovakia",
"sb": "Solomon Islands", "si": "Slovenia",
"so": "Somalia", "sb": "Solomon Islands",
"za": "South Africa", "so": "Somalia",
"gs": "South Georgia and the South Sandwich Islands", "za": "South Africa",
"ss": "South Sudan", "gs": "South Georgia and the South Sandwich Islands",
"es": "Spain", "ss": "South Sudan",
"lk": "Sri Lanka", "es": "Spain",
"sd": "Sudan", "lk": "Sri Lanka",
"sr": "Suriname", "sd": "Sudan",
"sj": "Svalbard and Jan Mayen", "sr": "Suriname",
"sz": "Swaziland", "sj": "Svalbard and Jan Mayen",
"se": "Sweden", "sz": "Swaziland",
"ch": "Switzerland", "se": "Sweden",
"sy": "Syrian Arab Republic", "ch": "Switzerland",
"tw": "Taiwan", "sy": "Syrian Arab Republic",
"tj": "Tajikistan", "tw": "Taiwan",
"tz": "Tanzania", "tj": "Tajikistan",
"th": "Thailand", "tz": "Tanzania",
"tl": "Timor-Leste", "th": "Thailand",
"tg": "Togo", "tl": "Timor-Leste",
"tk": "Tokelau", "tg": "Togo",
"to": "Tonga", "tk": "Tokelau",
"tt": "Trinidad and Tobago", "to": "Tonga",
"tn": "Tunisia", "tt": "Trinidad and Tobago",
"tr": "Turkey", "tn": "Tunisia",
"tm": "Turkmenistan", "tr": "Turkey",
"tc": "Turks and Caicos Islands", "tm": "Turkmenistan",
"tv": "Tuvalu", "tc": "Turks and Caicos Islands",
"ug": "Uganda", "tv": "Tuvalu",
"ua": "Ukraine", "ug": "Uganda",
"ae": "United Arab Emirates", "ua": "Ukraine",
"gb": "United Kingdom", "ae": "United Arab Emirates",
"uk": "United Kingdom", "gb": "United Kingdom",
"um": "United States Minor Outlying Islands", "uk": "United Kingdom",
"us": "United States", "um": "United States Minor Outlying Islands",
"uy": "Uruguay", "us": "United States",
"vi": "US Virgin Islands", "uy": "Uruguay",
"uz": "Uzbekistan", "vi": "US Virgin Islands",
"vu": "Vanuatu", "uz": "Uzbekistan",
"va": "Vatican City State", "vu": "Vanuatu",
"ve": "Venezuela", "va": "Vatican City State",
"vn": "Vietnam", "ve": "Venezuela",
"wf": "Wallis and Futuna", "vn": "Vietnam",
"eh": "Western Sahara", "wf": "Wallis and Futuna",
"ye": "Yemen", "eh": "Western Sahara",
"zm": "Zambia", "ye": "Yemen",
"zw": "Zimbabwe", "zm": "Zambia",
"zw": "Zimbabwe",
} }
} }

View File

@@ -71,6 +71,11 @@ func GetAllServers() (allServers models.AllServers) {
Timestamp: 1620611129, Timestamp: 1620611129,
Servers: TorguardServers(), Servers: TorguardServers(),
}, },
VPNUnlimited: models.VPNUnlimitedServers{
Version: 1,
Timestamp: 1623950304,
Servers: VPNUnlimitedServers(),
},
Vyprvpn: models.VyprvpnServers{ Vyprvpn: models.VyprvpnServers{
Version: 2, Version: 2,
Timestamp: 1620612506, Timestamp: 1620612506,

View File

@@ -100,6 +100,11 @@ func Test_versions(t *testing.T) {
version: allServers.Torguard.Version, version: allServers.Torguard.Version,
digest: "6eb9028e", digest: "6eb9028e",
}, },
"VPN Unlimited": {
model: models.VPNUnlimitedServer{},
version: allServers.VPNUnlimited.Version,
digest: "5cb51319",
},
"Vyprvpn": { "Vyprvpn": {
model: models.VyprvpnServer{}, model: models.VyprvpnServer{},
version: allServers.Vyprvpn.Version, version: allServers.Vyprvpn.Version,
@@ -212,6 +217,11 @@ func Test_timestamps(t *testing.T) {
timestamp: allServers.Torguard.Timestamp, timestamp: allServers.Torguard.Timestamp,
digest: "af54b9e8", digest: "af54b9e8",
}, },
"VPN Unlimited": {
servers: allServers.VPNUnlimited.Servers,
timestamp: allServers.VPNUnlimited.Timestamp,
digest: "f6ddb84c",
},
"Vyprvpn": { "Vyprvpn": {
servers: allServers.Vyprvpn.Servers, servers: allServers.Vyprvpn.Servers,
timestamp: allServers.Vyprvpn.Timestamp, timestamp: allServers.Vyprvpn.Timestamp,

View File

@@ -30,7 +30,7 @@ func TorguardCityChoices() (choices []string) {
return makeUnique(choices) return makeUnique(choices)
} }
func TorguardHostnamesChoices() (choices []string) { func TorguardHostnameChoices() (choices []string) {
servers := TorguardServers() servers := TorguardServers()
choices = make([]string, len(servers)) choices = make([]string, len(servers))
for i := range servers { for i := range servers {

View File

@@ -27,6 +27,8 @@ const (
Surfshark = "surfshark" Surfshark = "surfshark"
// Torguard is a VPN provider. // Torguard is a VPN provider.
Torguard = "torguard" Torguard = "torguard"
// VPNUnlimited is a VPN provider.
VPNUnlimited = "vpn unlimited"
// Vyprvpn is a VPN provider. // Vyprvpn is a VPN provider.
Vyprvpn = "vyprvpn" Vyprvpn = "vyprvpn"
// Windscribe is a VPN provider. // 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)) 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 { type VyprvpnServer struct {
Region string `json:"region"` Region string `json:"region"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`

View File

@@ -1,22 +1,23 @@
package models package models
type AllServers struct { type AllServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Cyberghost CyberghostServers `json:"cyberghost"` Cyberghost CyberghostServers `json:"cyberghost"`
Fastestvpn FastestvpnServers `json:"fastestvpn"` Fastestvpn FastestvpnServers `json:"fastestvpn"`
HideMyAss HideMyAssServers `json:"hidemyass"` HideMyAss HideMyAssServers `json:"hidemyass"`
Ivpn IvpnServers `json:"ivpn"` Ivpn IvpnServers `json:"ivpn"`
Mullvad MullvadServers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Nordvpn NordvpnServers `json:"nordvpn"` Nordvpn NordvpnServers `json:"nordvpn"`
Privado PrivadoServers `json:"privado"` Privado PrivadoServers `json:"privado"`
Pia PiaServers `json:"pia"` Pia PiaServers `json:"pia"`
Privatevpn PrivatevpnServers `json:"privatevpn"` Privatevpn PrivatevpnServers `json:"privatevpn"`
Protonvpn ProtonvpnServers `json:"protonvpn"` Protonvpn ProtonvpnServers `json:"protonvpn"`
Purevpn PurevpnServers `json:"purevpn"` Purevpn PurevpnServers `json:"purevpn"`
Surfshark SurfsharkServers `json:"surfshark"` Surfshark SurfsharkServers `json:"surfshark"`
Torguard TorguardServers `json:"torguard"` Torguard TorguardServers `json:"torguard"`
Vyprvpn VyprvpnServers `json:"vyprvpn"` VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
Windscribe WindscribeServers `json:"windscribe"` Vyprvpn VyprvpnServers `json:"vyprvpn"`
Windscribe WindscribeServers `json:"windscribe"`
} }
func (a *AllServers) Count() int { func (a *AllServers) Count() int {
@@ -33,6 +34,7 @@ func (a *AllServers) Count() int {
len(a.Purevpn.Servers) + len(a.Purevpn.Servers) +
len(a.Surfshark.Servers) + len(a.Surfshark.Servers) +
len(a.Torguard.Servers) + len(a.Torguard.Servers) +
len(a.VPNUnlimited.Servers) +
len(a.Vyprvpn.Servers) + len(a.Vyprvpn.Servers) +
len(a.Windscribe.Servers) len(a.Windscribe.Servers)
} }
@@ -102,6 +104,11 @@ type TorguardServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []TorguardServer `json:"servers"` Servers []TorguardServer `json:"servers"`
} }
type VPNUnlimitedServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []VPNUnlimitedServer `json:"servers"`
}
type VyprvpnServers struct { type VyprvpnServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -25,6 +25,7 @@ import (
"github.com/qdm12/gluetun/internal/provider/purevpn" "github.com/qdm12/gluetun/internal/provider/purevpn"
"github.com/qdm12/gluetun/internal/provider/surfshark" "github.com/qdm12/gluetun/internal/provider/surfshark"
"github.com/qdm12/gluetun/internal/provider/torguard" "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/vyprvpn"
"github.com/qdm12/gluetun/internal/provider/windscribe" "github.com/qdm12/gluetun/internal/provider/windscribe"
"github.com/qdm12/golibs/logging" "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) return surfshark.New(allServers.Surfshark.Servers, randSource)
case constants.Torguard: case constants.Torguard:
return torguard.New(allServers.Torguard.Servers, randSource) return torguard.New(allServers.Torguard.Servers, randSource)
case constants.VPNUnlimited:
return vpnunlimited.New(allServers.VPNUnlimited.Servers, randSource)
case constants.Vyprvpn: case constants.Vyprvpn:
return vyprvpn.New(allServers.Vyprvpn.Servers, randSource) return vyprvpn.New(allServers.Vyprvpn.Servers, randSource)
case constants.Windscribe: 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" "strconv"
"time" "time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "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 { func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.AllServers {
return models.AllServers{ return models.AllServers{
Version: hardcoded.Version, Version: hardcoded.Version,
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost), Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn), Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss), HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn), Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia), Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn), Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn), Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn),
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn), Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark), Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard), Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn), VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited),
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe), 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 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 { func (s *storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) models.VyprvpnServers {
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded return hardcoded

View File

@@ -31,6 +31,7 @@ func countServers(allServers models.AllServers) int {
len(allServers.Purevpn.Servers) + len(allServers.Purevpn.Servers) +
len(allServers.Surfshark.Servers) + len(allServers.Surfshark.Servers) +
len(allServers.Torguard.Servers) + len(allServers.Torguard.Servers) +
len(allServers.VPNUnlimited.Servers) +
len(allServers.Vyprvpn.Servers) + len(allServers.Vyprvpn.Servers) +
len(allServers.Windscribe.Servers) len(allServers.Windscribe.Servers)
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/updater/providers/cyberghost" "github.com/qdm12/gluetun/internal/updater/providers/cyberghost"
"github.com/qdm12/gluetun/internal/updater/providers/fastestvpn" "github.com/qdm12/gluetun/internal/updater/providers/fastestvpn"
"github.com/qdm12/gluetun/internal/updater/providers/hidemyass" "github.com/qdm12/gluetun/internal/updater/providers/hidemyass"
@@ -17,6 +18,7 @@ import (
"github.com/qdm12/gluetun/internal/updater/providers/purevpn" "github.com/qdm12/gluetun/internal/updater/providers/purevpn"
"github.com/qdm12/gluetun/internal/updater/providers/surfshark" "github.com/qdm12/gluetun/internal/updater/providers/surfshark"
"github.com/qdm12/gluetun/internal/updater/providers/torguard" "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/vyprvpn"
"github.com/qdm12/gluetun/internal/updater/providers/windscribe" "github.com/qdm12/gluetun/internal/updater/providers/windscribe"
) )
@@ -261,6 +263,26 @@ func (u *updater) updateTorguard(ctx context.Context) (err error) {
return nil 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) { func (u *updater) updateVyprvpn(ctx context.Context) (err error) {
minServers := getMinServers(len(u.servers.Vyprvpn.Servers)) minServers := getMinServers(len(u.servers.Vyprvpn.Servers))
servers, warnings, err := vyprvpn.GetServers( 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" "time"
"github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/updater/resolver" "github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip" "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 { if u.options.Vyprvpn {
u.logger.Info("updating Vyprvpn servers...") u.logger.Info("updating Vyprvpn servers...")
if err := u.updateVyprvpn(ctx); err != nil { if err := u.updateVyprvpn(ctx); err != nil {