Feature: PrivateVPN support (#393)

This commit is contained in:
Quentin McGaw
2021-03-05 22:58:57 -05:00
committed by GitHub
parent be72f4a046
commit 9509b855f1
24 changed files with 597 additions and 8 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package provider
const (
aes256cbc = "aes-256-cbc"
aes128gcm = "aes-128-gcm"
aes256gcm = "aes-256-gcm"
sha256 = "sha256"
)

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

View File

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

View File

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

View File

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

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

View File

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