Feature: PrivateVPN support (#393)
This commit is contained in:
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@@ -33,6 +33,9 @@
|
||||
- name: ":cloud: Privado"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: PrivateVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: PureVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Gluetun VPN client
|
||||
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost,
|
||||
HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PureVPN,
|
||||
Surfshark, TorGuard, VyprVPN and Windscribe VPN servers
|
||||
HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
||||
PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers
|
||||
using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
|
||||
@@ -39,7 +39,7 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
||||
- Supports: **Cyberghost**, **HideMyAss**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe**, servers
|
||||
- Supports: **Cyberghost**, **HideMyAss**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe**, servers
|
||||
- Supports Openvpn only for now
|
||||
- DNS over TLS baked in with service provider(s) of your choice
|
||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
|
||||
@@ -28,6 +28,7 @@ func (c *cli) Update(ctx context.Context, args []string, os os.OS) error {
|
||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
||||
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
|
||||
flagSet.BoolVar(&options.Privatevpn, "privatevpn", false, "Update Private VPN servers")
|
||||
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")
|
||||
|
||||
@@ -57,8 +57,8 @@ var (
|
||||
func (settings *OpenVPN) read(r reader) (err error) {
|
||||
vpnsp, err := r.env.Inside("VPNSP", []string{
|
||||
"cyberghost", "hidemyass", "mullvad", "nordvpn", "privado",
|
||||
"pia", "private internet access", "purevpn", "surfshark",
|
||||
"torguard", "vyprvpn", "windscribe"},
|
||||
"pia", "private internet access", "privatevpn",
|
||||
"purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"},
|
||||
params.Default("private internet access"))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -125,6 +125,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
|
||||
readProvider = settings.Provider.readPrivado
|
||||
case constants.PrivateInternetAccess:
|
||||
readProvider = settings.Provider.readPrivateInternetAccess
|
||||
case constants.Privatevpn:
|
||||
readProvider = settings.Provider.readPrivatevpn
|
||||
case constants.Purevpn:
|
||||
readProvider = settings.Provider.readPurevpn
|
||||
case constants.Surfshark:
|
||||
|
||||
52
internal/configuration/privatevpn.go
Normal file
52
internal/configuration/privatevpn.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) privatevpnLines() (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))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPrivatevpn(r reader) (err error) {
|
||||
settings.Name = constants.Privatevpn
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PrivatevpnCountryChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PrivatevpnCityChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PrivatevpnHostnameChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -39,6 +39,8 @@ func (settings *Provider) lines() (lines []string) {
|
||||
providerLines = settings.nordvpnLines()
|
||||
case "privado":
|
||||
providerLines = settings.privadoLines()
|
||||
case "privatevpn":
|
||||
providerLines = settings.privatevpnLines()
|
||||
case "private internet access":
|
||||
providerLines = settings.privateinternetaccessLines()
|
||||
case "purevpn":
|
||||
|
||||
@@ -114,6 +114,24 @@ func Test_Provider_lines(t *testing.T) {
|
||||
" |--Hostnames: a, b",
|
||||
},
|
||||
},
|
||||
"privatevpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Privatevpn,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Hostnames: []string{"a", "b"},
|
||||
Countries: []string{"c", "d"},
|
||||
Cities: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Privatevpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: c, d",
|
||||
" |--Cities: e, f",
|
||||
" |--Hostnames: a, b",
|
||||
},
|
||||
},
|
||||
"private internet access": {
|
||||
settings: Provider{
|
||||
Name: constants.PrivateInternetAccess,
|
||||
|
||||
@@ -15,9 +15,9 @@ type ServerSelection struct {
|
||||
// Cyberghost
|
||||
Group string `json:"group"`
|
||||
|
||||
Countries []string `json:"countries"` // HideMyAss, Mullvad, PureVPN
|
||||
Cities []string `json:"cities"` // HideMyAss, Mullvad, PureVPN, Windscribe
|
||||
Hostnames []string `json:"hostnames"` // HideMyAss, Windscribe, Privado
|
||||
Countries []string `json:"countries"` // HideMyAss, Mullvad, PrivateVPN, PureVPN
|
||||
Cities []string `json:"cities"` // HideMyAss, Mullvad, PrivateVPN, PureVPN, Windscribe
|
||||
Hostnames []string `json:"hostnames"` // HideMyAss, PrivateVPN, Windscribe, Privado
|
||||
|
||||
// Mullvad
|
||||
ISPs []string `json:"isps"`
|
||||
|
||||
@@ -16,6 +16,7 @@ type Updater struct {
|
||||
Nordvpn bool `json:"nordvpn"`
|
||||
PIA bool `json:"pia"`
|
||||
Privado bool `json:"privado"`
|
||||
Privatevpn bool `json:"privatevpn"`
|
||||
Purevpn bool `json:"purevpn"`
|
||||
Surfshark bool `json:"surfshark"`
|
||||
Torguard bool `json:"torguard"`
|
||||
@@ -49,6 +50,8 @@ func (settings *Updater) read(r reader) (err error) {
|
||||
settings.Nordvpn = true
|
||||
settings.Privado = true
|
||||
settings.PIA = true
|
||||
settings.Privado = true
|
||||
settings.Privatevpn = true
|
||||
settings.Purevpn = true
|
||||
settings.Surfshark = true
|
||||
settings.Torguard = true
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
// Package constants defines constants shared throughout the program.
|
||||
// It also defines constant maps and slices using functions.
|
||||
package constants
|
||||
|
||||
import "sort"
|
||||
|
||||
func makeChoicesUnique(choices []string) []string {
|
||||
uniqueChoices := map[string]struct{}{}
|
||||
for _, choice := range choices {
|
||||
uniqueChoices[choice] = struct{}{}
|
||||
}
|
||||
|
||||
uniqueChoicesSlice := make([]string, len(uniqueChoices))
|
||||
i := 0
|
||||
for choice := range uniqueChoices {
|
||||
uniqueChoicesSlice[i] = choice
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Slice(uniqueChoicesSlice, func(i, j int) bool {
|
||||
return uniqueChoicesSlice[i] < uniqueChoicesSlice[j]
|
||||
})
|
||||
|
||||
return uniqueChoicesSlice
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@ func CountryCodes() map[string]string {
|
||||
"ua": "Ukraine",
|
||||
"ae": "United Arab Emirates",
|
||||
"gb": "United Kingdom",
|
||||
"uk": "United Kingdom",
|
||||
"um": "United States Minor Outlying Islands",
|
||||
"us": "United States",
|
||||
"uy": "Uruguay",
|
||||
|
||||
120
internal/constants/privatevpn.go
Normal file
120
internal/constants/privatevpn.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
//nolint:lll
|
||||
const (
|
||||
PrivatevpnCertificate = "MIIErTCCA5WgAwIBAgIJAPp3HmtYGCIOMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJTRTELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN0b2NraG9sbTETMBEGA1UEChMKUHJpdmF0ZVZQTjEWMBQGA1UEAxMNUHJpdmF0ZVZQTiBDQTETMBEGA1UEKRMKUHJpdmF0ZVZQTjEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBwcml2YXR2cG4uc2UwHhcNMTcwNTI0MjAxNTM3WhcNMjcwNTIyMjAxNTM3WjCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwjqTWbKk85WN8nd1TaBgBnBHceQWosp8mMHr4xWMTLagWRcq2Modfy7RPnBo9kyn5j/ZZwL/21gLWJbxidurGyZZdEV9Wb5KQl3DUNxa19kwAbkkEchdES61e99MjmQlWq4vGPXAHjEuDxOZ906AXglCyAvQoXcYW0mNm9yybWllVp1aBrCaZQrNYr7eoFvolqJXdQQ3FFsTBCYa5bHJcKQLBfsiqdJ/BAxhNkQtcmWNSgLy16qoxQpCsxNCxAcYnasuL4rwOP+RazBkJTPXA/2neCJC5rt+sXR9CSfiXdJGwMpYso5m31ZEd7JL2+is0FeAZ6ETrKMnEZMsTpTkdwIDAQABo4H9MIH6MB0GA1UdDgQWBBRCkBlC94zCY6VNncMnK36JxT7bazCBygYDVR0jBIHCMIG/gBRCkBlC94zCY6VNncMnK36JxT7ba6GBm6SBmDCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlggkA+ncea1gYIg4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAayugvExKDHar7t1zyYn99Vt1NMf46J8x4Dt9TNjBml5mR9nKvWmreMUuuOhLaO8Da466KGdXeDFNLcBYZd/J2iTawE6/3fmrML9H2sa+k/+E4uU5nQ84ZGOwCinCkMalVjM8EZ0/H2RZvLAVUnvPuUz2JfJhmiRkbeE75fVuqpAm9qdE+/7lg3oICYzxa6BJPxT+Imdjy3Q/FWdsXqX6aallhohPAZlMZgZL4eXECnV8rAfzyjOJggkMDZQt3Flc0Y4iDMfzrEhSOWMkNFBFwjK0F/dnhsX+fPX6GGRpUZgZcCt/hWvypqc05/SnrdKM/vV/jV/yZe0NVzY7S8Ur5g=="
|
||||
PrivatevpnOpenvpnStaticKeyV1 = "a49082f082ca89d6a6bb4ecc7c047c6d428a1d3c8254a95206d38a61d7fbe65984214cd7d56eacc5a60803bffd677fa7294d4bfe555036339312de2dfb1335bd9d5fd94b04bba3a15fc5192aeb02fb6d8dd2ca831fad7509be5eefa8d1eaa689dc586c831a23b589c512662652ecf1bb3a4a673816aba434a04f6857b8c2f8bb265bfe48a7b8112539729d2f7d9734a720e1035188118c73fef1824d0237d5579ca382d703b4bb252acaedc753b12199f00154d3769efbcf85ef5ad6ee755cbeaa944cb98e7654286df54c793a8443f5363078e3da548ba0beed079df633283cefb256f6a4bcfc4ab2c4affc24955c1864d5458e84a7c210d0d186269e55dcf6"
|
||||
)
|
||||
|
||||
func PrivatevpnCountryChoices() (choices []string) {
|
||||
servers := PrivatevpnServers()
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Country
|
||||
}
|
||||
return makeChoicesUnique(choices)
|
||||
}
|
||||
|
||||
func PrivatevpnCityChoices() (choices []string) {
|
||||
servers := PrivatevpnServers()
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].City
|
||||
}
|
||||
return makeChoicesUnique(choices)
|
||||
}
|
||||
|
||||
func PrivatevpnHostnameChoices() (choices []string) {
|
||||
servers := PrivatevpnServers()
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Hostname
|
||||
}
|
||||
return makeChoicesUnique(choices)
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
// PrivatevpnServers returns a slice of all the server information for Privatevpn.
|
||||
func PrivatevpnServers() []models.PrivatevpnServer {
|
||||
return []models.PrivatevpnServer{
|
||||
{Country: "Argentina", City: "Buenos Aires", Hostname: "ar-bue.pvdata.host", IPs: []net.IP{{181, 119, 160, 59}}},
|
||||
{Country: "Australia", City: "Melbourne", Hostname: "au-mel.pvdata.host", IPs: []net.IP{{103, 231, 88, 203}}},
|
||||
{Country: "Australia", City: "Sydney", Hostname: "au-syd.pvdata.host", IPs: []net.IP{{143, 244, 63, 96}}},
|
||||
{Country: "Austria", City: "Wien", Hostname: "at-wie.pvdata.host", IPs: []net.IP{{185, 9, 19, 91}}},
|
||||
{Country: "Belgium", City: "Brussels", Hostname: "be-bru.pvdata.host", IPs: []net.IP{{185, 104, 186, 211}}},
|
||||
{Country: "Brazil", City: "Sao Paulo", Hostname: "br-sao.pvdata.host", IPs: []net.IP{{45, 162, 230, 59}}},
|
||||
{Country: "Bulgaria", City: "Sofia", Hostname: "bg-sof.pvdata.host", IPs: []net.IP{{185, 94, 192, 163}}},
|
||||
{Country: "Canada", City: "Montreal", Hostname: "ca-mon.pvdata.host", IPs: []net.IP{{37, 120, 237, 163}, {87, 101, 92, 131}}},
|
||||
{Country: "Canada", City: "Toronto", Hostname: "ca-tor.pvdata.host", IPs: []net.IP{{45, 148, 7, 3}, {45, 148, 7, 6}, {45, 148, 7, 8}}},
|
||||
{Country: "Canada", City: "Vancouver", Hostname: "ca-van.pvdata.host", IPs: []net.IP{{74, 3, 160, 19}}},
|
||||
{Country: "Chile", City: "Santiago", Hostname: "cl-san.pvdata.host", IPs: []net.IP{{216, 241, 14, 227}}},
|
||||
{Country: "Costa Rica", City: "San Jose", Hostname: "cr-san.pvdata.host", IPs: []net.IP{{190, 10, 8, 218}}},
|
||||
{Country: "Croatia", City: "Zagreb", Hostname: "hr-zag.pvdata.host", IPs: []net.IP{{85, 10, 56, 127}}},
|
||||
{Country: "Cyprus", City: "Nicosia", Hostname: "cy-nic.pvdata.host", IPs: []net.IP{{185, 173, 226, 47}}},
|
||||
{Country: "Czech Republic", City: "Prague", Hostname: "cz-pra.pvdata.host", IPs: []net.IP{{185, 156, 174, 179}}},
|
||||
{Country: "Denmark", City: "Copenhagen", Hostname: "dk-cop.pvdata.host", IPs: []net.IP{{62, 115, 255, 188}, {62, 115, 255, 189}}},
|
||||
{Country: "France", City: "Paris", Hostname: "fr-par.pvdata.host", IPs: []net.IP{{80, 239, 199, 102}, {80, 239, 199, 103}, {80, 239, 199, 104}, {80, 239, 199, 105}}},
|
||||
{Country: "Germany", City: "Frankfurt", Hostname: "de-fra.pvdata.host", IPs: []net.IP{{193, 180, 119, 130}, {193, 180, 119, 131}}},
|
||||
{Country: "Germany", City: "Nuremberg", Hostname: "de-nur.pvdata.host", IPs: []net.IP{{185, 89, 36, 3}}},
|
||||
{Country: "Greece", City: "Athens", Hostname: "gr-ath.pvdata.host", IPs: []net.IP{{154, 57, 3, 33}}},
|
||||
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hk-hon.pvdata.host", IPs: []net.IP{{84, 17, 37, 58}}},
|
||||
{Country: "Hungary", City: "Budapest", Hostname: "hu-bud.pvdata.host", IPs: []net.IP{{185, 104, 187, 67}}},
|
||||
{Country: "Iceland", City: "Reykjavik", Hostname: "is-rey.pvdata.host", IPs: []net.IP{{82, 221, 113, 210}}},
|
||||
{Country: "Indonesia", City: "Jakarta", Hostname: "id-jak.pvdata.host", IPs: []net.IP{{23, 248, 170, 136}}},
|
||||
{Country: "Ireland", City: "Dublin", Hostname: "ie-dub.pvdata.host", IPs: []net.IP{{217, 138, 222, 67}}},
|
||||
{Country: "Isle of Man", City: "Ballasalla", Hostname: "im-bal.pvdata.host", IPs: []net.IP{{81, 27, 96, 89}}},
|
||||
{Country: "Italy", City: "Milan", Hostname: "it-mil.pvdata.host", IPs: []net.IP{{217, 212, 240, 90}, {217, 212, 240, 91}, {217, 212, 240, 92}, {217, 212, 240, 93}}},
|
||||
{Country: "Japan", City: "Tokyo", Hostname: "jp-tok.pvdata.host", IPs: []net.IP{{89, 187, 160, 154}}},
|
||||
{Country: "Korea", City: "Seoul", Hostname: "kr-seo.pvdata.host", IPs: []net.IP{{92, 223, 73, 37}}},
|
||||
{Country: "Latvia", City: "Riga", Hostname: "lv-rig.pvdata.host", IPs: []net.IP{{80, 233, 134, 165}}},
|
||||
{Country: "Lithuania", City: "Siauliai", Hostname: "lt-sia.pvdata.host", IPs: []net.IP{{5, 199, 171, 93}}},
|
||||
{Country: "Luxembourg", City: "Steinsel", Hostname: "lu-ste.pvdata.host", IPs: []net.IP{{94, 242, 250, 71}}},
|
||||
{Country: "Malaysia", City: "Kuala Lumpur", Hostname: "my-kua.pvdata.host", IPs: []net.IP{{128, 1, 160, 184}}},
|
||||
{Country: "Malta", City: "Qormi", Hostname: "mt-qor.pvdata.host", IPs: []net.IP{{130, 185, 255, 25}}},
|
||||
{Country: "Mexico", City: "Mexico City", Hostname: "mx-mex.pvdata.host", IPs: []net.IP{{190, 60, 16, 28}}},
|
||||
{Country: "Moldova", City: "Chisinau", Hostname: "md-chi.pvdata.host", IPs: []net.IP{{178, 17, 172, 99}}},
|
||||
{Country: "Netherlands", City: "Amsterdam", Hostname: "nl-ams.pvdata.host", IPs: []net.IP{{193, 180, 119, 194}, {193, 180, 119, 195}, {193, 180, 119, 196}, {193, 180, 119, 197}}},
|
||||
{Country: "New Zealand", City: "Auckland", Hostname: "nz-auc.pvdata.host", IPs: []net.IP{{45, 252, 191, 34}}},
|
||||
{Country: "Norway", City: "Oslo", Hostname: "no-osl.pvdata.host", IPs: []net.IP{{91, 205, 186, 26}}},
|
||||
{Country: "Panama", City: "Panama City", Hostname: "pa-pan.pvdata.host", IPs: []net.IP{{200, 110, 155, 235}}},
|
||||
{Country: "Peru", City: "Lima", Hostname: "pe-lim.pvdata.host", IPs: []net.IP{{170, 0, 81, 107}}},
|
||||
{Country: "Philippines", City: "Manila", Hostname: "ph-man.pvdata.host", IPs: []net.IP{{128, 1, 209, 12}}},
|
||||
{Country: "Portugal", City: "Lisbon", Hostname: "pt-lis.pvdata.host", IPs: []net.IP{{130, 185, 85, 107}}},
|
||||
{Country: "Romania", City: "Bukarest", Hostname: "ro-buk.pvdata.host", IPs: []net.IP{{89, 40, 181, 203}}},
|
||||
{Country: "Russian Federation", City: "Krasnoyarsk", Hostname: "ru-kra.pvdata.host", IPs: []net.IP{{92, 223, 87, 11}}},
|
||||
{Country: "Russian Federation", City: "Moscow", Hostname: "ru-mos.pvdata.host", IPs: []net.IP{{92, 223, 103, 138}}},
|
||||
{Country: "Russian Federation", City: "St Petersburg", Hostname: "ru-pet.pvdata.host", IPs: []net.IP{{95, 213, 148, 99}}},
|
||||
{Country: "Serbia", City: "Belgrade", Hostname: "rs-bel.pvdata.host", IPs: []net.IP{{141, 98, 103, 166}}},
|
||||
{Country: "Slovakia", City: "Bratislava", Hostname: "sg-sin.pvdata.host", IPs: []net.IP{{143, 244, 33, 81}}},
|
||||
{Country: "Spain", City: "Madrid", Hostname: "es-mad.pvdata.host", IPs: []net.IP{{217, 212, 244, 92}, {217, 212, 244, 93}}},
|
||||
{Country: "Sweden", City: "Gothenburg", Hostname: "se-got.pvdata.host", IPs: []net.IP{{193, 187, 91, 19}}},
|
||||
{Country: "Sweden", City: "Kista", Hostname: "se-kis.pvdata.host", IPs: []net.IP{{193, 187, 88, 216}, {193, 187, 88, 217}, {193, 187, 88, 218}, {193, 187, 88, 219}, {193, 187, 88, 220}, {193, 187, 88, 221}, {193, 187, 88, 222}}},
|
||||
{Country: "Sweden", City: "Stockholm", Hostname: "se-sto.pvdata.host", IPs: []net.IP{{193, 180, 119, 2}, {193, 180, 119, 6}, {193, 180, 119, 7}}},
|
||||
{Country: "Switzerland", City: "Zurich", Hostname: "ch-zur.pvdata.host", IPs: []net.IP{{217, 212, 245, 92}, {217, 212, 245, 93}}},
|
||||
{Country: "Taiwan", City: "Taipei", Hostname: "tw-tai.pvdata.host", IPs: []net.IP{{2, 58, 241, 51}}},
|
||||
{Country: "Thailand", City: "Bangkok", Hostname: "th-ban.pvdata.host", IPs: []net.IP{{103, 27, 203, 234}}},
|
||||
{Country: "Turkey", City: "Istanbul", Hostname: "tr-ist.pvdata.host", IPs: []net.IP{{92, 38, 180, 28}}},
|
||||
{Country: "Ukraine", City: "Kiev", Hostname: "ua-kie.pvdata.host", IPs: []net.IP{{192, 121, 68, 131}}},
|
||||
{Country: "Ukraine", City: "Nikolaev", Hostname: "ua-nik.pvdata.host", IPs: []net.IP{{194, 54, 83, 21}}},
|
||||
{Country: "United Arab Emirates", City: "Dubai", Hostname: "ae-dub.pvdata.host", IPs: []net.IP{{45, 9, 249, 59}}},
|
||||
{Country: "United Kingdom", City: "London", Hostname: "uk-lon.pvdata.host", IPs: []net.IP{{193, 180, 119, 66}, {193, 180, 119, 67}, {193, 180, 119, 68}, {193, 180, 119, 69}, {193, 180, 119, 70}}},
|
||||
{Country: "United Kingdom", City: "London", Hostname: "uk-lon2.pvdata.host", IPs: []net.IP{{185, 41, 242, 67}}},
|
||||
{Country: "United Kingdom", City: "London", Hostname: "uk-lon7.pvdata.host", IPs: []net.IP{{185, 125, 204, 179}}},
|
||||
{Country: "United Kingdom", City: "Manchester", Hostname: "uk-man.pvdata.host", IPs: []net.IP{{185, 206, 227, 181}}},
|
||||
{Country: "United States", City: "Buffalo", Hostname: "us-buf.pvdata.host", IPs: []net.IP{{172, 245, 13, 115}, {192, 210, 199, 35}}},
|
||||
{Country: "United States", City: "Chicago", Hostname: "us-chi.pvdata.host", IPs: []net.IP{{185, 93, 1, 114}}},
|
||||
{Country: "United States", City: "Dallas", Hostname: "us-dal.pvdata.host", IPs: []net.IP{{89, 187, 164, 97}}},
|
||||
{Country: "United States", City: "Las Vegas", Hostname: "us-las.pvdata.host", IPs: []net.IP{{82, 102, 30, 19}}},
|
||||
{Country: "United States", City: "Los Angeles", Hostname: "us-los.pvdata.host", IPs: []net.IP{{89, 187, 185, 78}, {185, 152, 67, 132}}},
|
||||
{Country: "United States", City: "Miami", Hostname: "us-mia.pvdata.host", IPs: []net.IP{{195, 181, 163, 139}}},
|
||||
{Country: "United States", City: "New York", Hostname: "us-nyc.pvdata.host", IPs: []net.IP{{45, 130, 86, 3}, {45, 130, 86, 5}, {45, 130, 86, 8}, {45, 130, 86, 10}, {45, 130, 86, 12}}},
|
||||
{Country: "United States", City: "Phoenix", Hostname: "us-pho.pvdata.host", IPs: []net.IP{{82, 102, 30, 131}}},
|
||||
{Country: "Vietnam", City: "Ho Chi Minh City", Hostname: "vn-hoc.pvdata.host", IPs: []net.IP{{210, 2, 64, 5}}},
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,11 @@ func GetAllServers() (allServers models.AllServers) {
|
||||
Timestamp: 1612031135,
|
||||
Servers: PrivadoServers(),
|
||||
},
|
||||
Privatevpn: models.PrivatevpnServers{
|
||||
Version: 1,
|
||||
Timestamp: 1613861528,
|
||||
Servers: PrivatevpnServers(),
|
||||
},
|
||||
Pia: models.PiaServers{
|
||||
Version: 4,
|
||||
Timestamp: 1613480675,
|
||||
|
||||
@@ -64,6 +64,11 @@ func Test_versions(t *testing.T) {
|
||||
version: allServers.Pia.Version,
|
||||
digest: "3e6066ec",
|
||||
},
|
||||
"Privatevpn": {
|
||||
model: models.PrivatevpnServer{},
|
||||
version: allServers.Privatevpn.Version,
|
||||
digest: "cba13d78",
|
||||
},
|
||||
"Purevpn": {
|
||||
model: models.PurevpnServer{},
|
||||
version: allServers.Purevpn.Version,
|
||||
@@ -155,6 +160,11 @@ func Test_timestamps(t *testing.T) {
|
||||
timestamp: allServers.Pia.Timestamp,
|
||||
digest: "e0f95a01",
|
||||
},
|
||||
"Privatevpn": {
|
||||
servers: allServers.Privatevpn.Servers,
|
||||
timestamp: allServers.Privatevpn.Timestamp,
|
||||
digest: "8ce3fba1",
|
||||
},
|
||||
"Purevpn": {
|
||||
servers: allServers.Purevpn.Servers,
|
||||
timestamp: allServers.Purevpn.Timestamp,
|
||||
|
||||
@@ -13,6 +13,8 @@ const (
|
||||
Privado = "privado"
|
||||
// PrivateInternetAccess is a VPN provider.
|
||||
PrivateInternetAccess = "private internet access"
|
||||
// Privatevpn is a VPN provider.
|
||||
Privatevpn = "privatevpn"
|
||||
// PureVPN is a VPN provider.
|
||||
Purevpn = "purevpn"
|
||||
// Surfshark is a VPN provider.
|
||||
|
||||
@@ -137,6 +137,18 @@ func (s *WindscribeServer) String() string {
|
||||
s.Region, s.City, s.Hostname, goStringifyIP(s.IP))
|
||||
}
|
||||
|
||||
type PrivatevpnServer struct {
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
Hostname string `json:"hostname"`
|
||||
IPs []net.IP `json:"ip"`
|
||||
}
|
||||
|
||||
func (s *PrivatevpnServer) String() string {
|
||||
return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, IPs: %s}",
|
||||
s.Country, s.City, s.Hostname, goStringifyIPs(s.IPs))
|
||||
}
|
||||
|
||||
func goStringifyIP(ip net.IP) string {
|
||||
s := fmt.Sprintf("%#v", ip)
|
||||
s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}")
|
||||
|
||||
@@ -8,6 +8,7 @@ type AllServers struct {
|
||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||
Privado PrivadoServers `json:"privado"`
|
||||
Pia PiaServers `json:"pia"`
|
||||
Privatevpn PrivatevpnServers `json:"privatevpn"`
|
||||
Purevpn PurevpnServers `json:"purevpn"`
|
||||
Surfshark SurfsharkServers `json:"surfshark"`
|
||||
Torguard TorguardServers `json:"torguard"`
|
||||
@@ -22,6 +23,7 @@ func (a *AllServers) Count() int {
|
||||
len(a.Nordvpn.Servers) +
|
||||
len(a.Privado.Servers) +
|
||||
len(a.Pia.Servers) +
|
||||
len(a.Privatevpn.Servers) +
|
||||
len(a.Purevpn.Servers) +
|
||||
len(a.Surfshark.Servers) +
|
||||
len(a.Torguard.Servers) +
|
||||
@@ -59,6 +61,11 @@ type PiaServers struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PIAServer `json:"servers"`
|
||||
}
|
||||
type PrivatevpnServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PrivatevpnServer `json:"servers"`
|
||||
}
|
||||
type PurevpnServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
|
||||
@@ -2,6 +2,7 @@ package provider
|
||||
|
||||
const (
|
||||
aes256cbc = "aes-256-cbc"
|
||||
aes128gcm = "aes-128-gcm"
|
||||
aes256gcm = "aes-256-gcm"
|
||||
sha256 = "sha256"
|
||||
)
|
||||
|
||||
165
internal/provider/privatevpn.go
Normal file
165
internal/provider/privatevpn.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/firewall"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type privatevpn struct {
|
||||
servers []models.PrivatevpnServer
|
||||
randSource rand.Source
|
||||
}
|
||||
|
||||
func newPrivatevpn(servers []models.PrivatevpnServer, timeNow timeNowFunc) *privatevpn {
|
||||
return &privatevpn{
|
||||
servers: servers,
|
||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *privatevpn) filterServers(countries, cities, hostnames []string) (servers []models.PrivatevpnServer) {
|
||||
for _, server := range p.servers {
|
||||
switch {
|
||||
case
|
||||
filterByPossibilities(server.Country, countries),
|
||||
filterByPossibilities(server.City, cities),
|
||||
filterByPossibilities(server.Hostname, hostnames):
|
||||
default:
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (p *privatevpn) notFoundErr(selection configuration.ServerSelection) error {
|
||||
message := "no server found for protocol " + selection.Protocol
|
||||
|
||||
if len(selection.Countries) > 0 {
|
||||
message += " + countries " + commaJoin(selection.Countries)
|
||||
}
|
||||
|
||||
if len(selection.Cities) > 0 {
|
||||
message += " + cities " + commaJoin(selection.Cities)
|
||||
}
|
||||
|
||||
if len(selection.Hostnames) > 0 {
|
||||
message += " + hostnames " + commaJoin(selection.Hostnames)
|
||||
}
|
||||
|
||||
return fmt.Errorf(message)
|
||||
}
|
||||
|
||||
func (p *privatevpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||
connection models.OpenVPNConnection, err error) {
|
||||
var port uint16
|
||||
if selection.Protocol == constants.TCP {
|
||||
port = 443
|
||||
} else {
|
||||
port = 1194
|
||||
}
|
||||
|
||||
if selection.TargetIP != nil {
|
||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||
}
|
||||
|
||||
servers := p.filterServers(selection.Countries, selection.Cities, selection.Hostnames)
|
||||
if len(servers) == 0 {
|
||||
return connection, p.notFoundErr(selection)
|
||||
}
|
||||
|
||||
var connections []models.OpenVPNConnection
|
||||
for _, server := range servers {
|
||||
for _, ip := range server.IPs {
|
||||
connection := models.OpenVPNConnection{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
Protocol: selection.Protocol,
|
||||
}
|
||||
connections = append(connections, connection)
|
||||
}
|
||||
}
|
||||
|
||||
return pickRandomConnection(connections, p.randSource), nil
|
||||
}
|
||||
|
||||
func (p *privatevpn) BuildConf(connection models.OpenVPNConnection,
|
||||
username string, settings configuration.OpenVPN) (lines []string) {
|
||||
if len(settings.Cipher) == 0 {
|
||||
settings.Cipher = aes128gcm
|
||||
}
|
||||
if len(settings.Auth) == 0 {
|
||||
settings.Auth = sha256
|
||||
}
|
||||
|
||||
lines = []string{
|
||||
"client",
|
||||
"dev tun",
|
||||
"nobind",
|
||||
"persist-key",
|
||||
"remote-cert-tls server",
|
||||
"tls-exit",
|
||||
|
||||
// Privatevpn specific
|
||||
"comp-lzo",
|
||||
"tun-ipv6",
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
"mute-replay-warnings",
|
||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||
"pull-filter ignore \"block-outside-dns\"",
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
|
||||
// Modified variables
|
||||
fmt.Sprintf("verb %d", settings.Verbosity),
|
||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||
fmt.Sprintf("proto %s", connection.Protocol),
|
||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||
fmt.Sprintf("cipher %s", settings.Cipher),
|
||||
fmt.Sprintf("auth %s", settings.Auth),
|
||||
}
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "key-direction 1")
|
||||
}
|
||||
if !settings.Root {
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
if settings.MSSFix > 0 {
|
||||
line := "mssfix " + strconv.Itoa(int(settings.MSSFix))
|
||||
lines = append(lines, line)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
"-----BEGIN CERTIFICATE-----",
|
||||
constants.PrivatevpnCertificate,
|
||||
"-----END CERTIFICATE-----",
|
||||
"</ca>",
|
||||
}...)
|
||||
lines = append(lines, []string{
|
||||
"<tls-crypt>",
|
||||
"-----BEGIN OpenVPN Static key V1-----",
|
||||
constants.PrivatevpnOpenvpnStaticKeyV1,
|
||||
"-----END OpenVPN Static key V1-----",
|
||||
"</tls-crypt>",
|
||||
"",
|
||||
}...)
|
||||
return lines
|
||||
}
|
||||
|
||||
func (p *privatevpn) 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 privatevpn")
|
||||
}
|
||||
@@ -37,6 +37,8 @@ func New(provider string, allServers models.AllServers, timeNow timeNowFunc) Pro
|
||||
return newPrivado(allServers.Privado.Servers, timeNow)
|
||||
case constants.PrivateInternetAccess:
|
||||
return newPrivateInternetAccess(allServers.Pia.Servers, timeNow)
|
||||
case constants.Privatevpn:
|
||||
return newPrivatevpn(allServers.Privatevpn.Servers, timeNow)
|
||||
case constants.Purevpn:
|
||||
return newPurevpn(allServers.Purevpn.Servers, timeNow)
|
||||
case constants.Surfshark:
|
||||
|
||||
@@ -23,6 +23,7 @@ func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
|
||||
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
|
||||
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
|
||||
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
|
||||
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
|
||||
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
|
||||
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
|
||||
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
|
||||
@@ -106,6 +107,22 @@ func (s *storage) mergePIA(hardcoded, persisted models.PiaServers) models.PiaSer
|
||||
return persisted
|
||||
}
|
||||
|
||||
func (s *storage) mergePrivatevpn(hardcoded, persisted models.PrivatevpnServers) models.PrivatevpnServers {
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := hardcoded.Version - persisted.Version
|
||||
if versionDiff > 0 {
|
||||
s.logger.Info(
|
||||
"Privatevpn servers from file discarded because they are %d versions behind",
|
||||
versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
s.logger.Info("Using Privatevpn servers from file (%s more recent)",
|
||||
getUnixTimeDifference(persisted.Timestamp, hardcoded.Timestamp))
|
||||
return persisted
|
||||
}
|
||||
|
||||
func (s *storage) mergePureVPN(hardcoded, persisted models.PurevpnServers) models.PurevpnServers {
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
|
||||
@@ -23,6 +23,7 @@ func countServers(allServers models.AllServers) int {
|
||||
len(allServers.Nordvpn.Servers) +
|
||||
len(allServers.Privado.Servers) +
|
||||
len(allServers.Pia.Servers) +
|
||||
len(allServers.Privatevpn.Servers) +
|
||||
len(allServers.Purevpn.Servers) +
|
||||
len(allServers.Surfshark.Servers) +
|
||||
len(allServers.Torguard.Servers) +
|
||||
|
||||
133
internal/updater/privatevpn.go
Normal file
133
internal/updater/privatevpn.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updatePrivatevpn(ctx context.Context) (err error) {
|
||||
servers, warnings, err := findPrivatevpnServersFromZip(ctx, u.client, u.lookupIP)
|
||||
if u.options.CLI {
|
||||
for _, warning := range warnings {
|
||||
u.logger.Warn("Privatevpn: %s", warning)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Privatevpn servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyPrivatevpnServers(servers))
|
||||
}
|
||||
u.servers.Privatevpn.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Privatevpn.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPrivatevpnServersFromZip(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) (
|
||||
servers []models.PrivatevpnServer, warnings []string, err error) {
|
||||
// Note: all servers do both TCP and UDP
|
||||
const zipURL = "https://privatevpn.com/client/PrivateVPN-TUN.zip"
|
||||
|
||||
contents, err := fetchAndExtractFiles(ctx, client, zipURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
trailingNumber := regexp.MustCompile(` [0-9]+$`)
|
||||
countryCodes := constants.CountryCodes()
|
||||
|
||||
uniqueServers := map[string]models.PrivatevpnServer{} // key is the hostname
|
||||
|
||||
for fileName, content := range contents {
|
||||
const prefix = "PrivateVPN-"
|
||||
const suffix = "-TUN-443.ovpn"
|
||||
|
||||
if !strings.HasSuffix(fileName, suffix) {
|
||||
continue // only process TCP servers as they're the same
|
||||
}
|
||||
|
||||
var server models.PrivatevpnServer
|
||||
|
||||
s := strings.TrimPrefix(fileName, prefix)
|
||||
s = strings.TrimSuffix(s, suffix)
|
||||
s = trailingNumber.ReplaceAllString(s, "")
|
||||
|
||||
parts := strings.Split(s, "-")
|
||||
var countryCode string
|
||||
countryCode, server.City = parts[0], parts[1]
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
var countryCodeOK bool
|
||||
server.Country, countryCodeOK = countryCodes[countryCode]
|
||||
if !countryCodeOK {
|
||||
warnings = append(warnings, "unknown country code: "+countryCode)
|
||||
server.Country = countryCode
|
||||
}
|
||||
|
||||
var warning string
|
||||
server.Hostname, warning, err = extractHostFromOVPN(content)
|
||||
if len(warning) > 0 {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
if len(warning) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
uniqueServers[server.Hostname] = server
|
||||
}
|
||||
|
||||
hostnames := make([]string, len(uniqueServers))
|
||||
i := 0
|
||||
for hostname := range uniqueServers {
|
||||
hostnames[i] = hostname
|
||||
i++
|
||||
}
|
||||
|
||||
const failOnError = false
|
||||
hostToIPs, newWarnings, _ := parallelResolve(ctx, lookupIP, hostnames, 5, time.Second, failOnError)
|
||||
if len(newWarnings) > 0 {
|
||||
warnings = append(warnings, newWarnings...)
|
||||
}
|
||||
|
||||
for hostname, server := range uniqueServers {
|
||||
ips := hostToIPs[hostname]
|
||||
if len(ips) == 0 {
|
||||
continue
|
||||
}
|
||||
server.IPs = ips
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
func stringifyPrivatevpnServers(servers []models.PrivatevpnServer) (s string) {
|
||||
s = "func PrivatevpnServers() []models.PrivatevpnServer {\n"
|
||||
s += " return []models.PrivatevpnServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
@@ -111,6 +111,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Privatevpn {
|
||||
u.logger.Info("updating Privatevpn servers...")
|
||||
if err := u.updatePrivatevpn(ctx); err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return allServers, ctxErr
|
||||
}
|
||||
u.logger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Purevpn {
|
||||
u.logger.Info("updating PureVPN servers...")
|
||||
// TODO support servers offering only TCP or only UDP
|
||||
|
||||
Reference in New Issue
Block a user