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"
|
- name: ":cloud: Privado"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: PrivateVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: PureVPN"
|
- name: ":cloud: PureVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost,
|
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost,
|
||||||
HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PureVPN,
|
HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
||||||
Surfshark, TorGuard, VyprVPN and Windscribe VPN servers
|
PureVPN, Surfshark, TorGuard, 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**: *New Docker image name `qmcgaw/gluetun`*
|
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
|
||||||
@@ -39,7 +39,7 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
- 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
|
- 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
|
||||||
|
|||||||
@@ -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.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 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.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.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")
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ var (
|
|||||||
func (settings *OpenVPN) read(r reader) (err error) {
|
func (settings *OpenVPN) read(r reader) (err error) {
|
||||||
vpnsp, err := r.env.Inside("VPNSP", []string{
|
vpnsp, err := r.env.Inside("VPNSP", []string{
|
||||||
"cyberghost", "hidemyass", "mullvad", "nordvpn", "privado",
|
"cyberghost", "hidemyass", "mullvad", "nordvpn", "privado",
|
||||||
"pia", "private internet access", "purevpn", "surfshark",
|
"pia", "private internet access", "privatevpn",
|
||||||
"torguard", "vyprvpn", "windscribe"},
|
"purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"},
|
||||||
params.Default("private internet access"))
|
params.Default("private internet access"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -125,6 +125,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
|
|||||||
readProvider = settings.Provider.readPrivado
|
readProvider = settings.Provider.readPrivado
|
||||||
case constants.PrivateInternetAccess:
|
case constants.PrivateInternetAccess:
|
||||||
readProvider = settings.Provider.readPrivateInternetAccess
|
readProvider = settings.Provider.readPrivateInternetAccess
|
||||||
|
case constants.Privatevpn:
|
||||||
|
readProvider = settings.Provider.readPrivatevpn
|
||||||
case constants.Purevpn:
|
case constants.Purevpn:
|
||||||
readProvider = settings.Provider.readPurevpn
|
readProvider = settings.Provider.readPurevpn
|
||||||
case constants.Surfshark:
|
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()
|
providerLines = settings.nordvpnLines()
|
||||||
case "privado":
|
case "privado":
|
||||||
providerLines = settings.privadoLines()
|
providerLines = settings.privadoLines()
|
||||||
|
case "privatevpn":
|
||||||
|
providerLines = settings.privatevpnLines()
|
||||||
case "private internet access":
|
case "private internet access":
|
||||||
providerLines = settings.privateinternetaccessLines()
|
providerLines = settings.privateinternetaccessLines()
|
||||||
case "purevpn":
|
case "purevpn":
|
||||||
|
|||||||
@@ -114,6 +114,24 @@ func Test_Provider_lines(t *testing.T) {
|
|||||||
" |--Hostnames: a, b",
|
" |--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": {
|
"private internet access": {
|
||||||
settings: Provider{
|
settings: Provider{
|
||||||
Name: constants.PrivateInternetAccess,
|
Name: constants.PrivateInternetAccess,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ type ServerSelection struct {
|
|||||||
// Cyberghost
|
// Cyberghost
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
|
|
||||||
Countries []string `json:"countries"` // HideMyAss, Mullvad, PureVPN
|
Countries []string `json:"countries"` // HideMyAss, Mullvad, PrivateVPN, PureVPN
|
||||||
Cities []string `json:"cities"` // HideMyAss, Mullvad, PureVPN, Windscribe
|
Cities []string `json:"cities"` // HideMyAss, Mullvad, PrivateVPN, PureVPN, Windscribe
|
||||||
Hostnames []string `json:"hostnames"` // HideMyAss, Windscribe, Privado
|
Hostnames []string `json:"hostnames"` // HideMyAss, PrivateVPN, Windscribe, Privado
|
||||||
|
|
||||||
// Mullvad
|
// Mullvad
|
||||||
ISPs []string `json:"isps"`
|
ISPs []string `json:"isps"`
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Updater struct {
|
|||||||
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"`
|
||||||
Purevpn bool `json:"purevpn"`
|
Purevpn bool `json:"purevpn"`
|
||||||
Surfshark bool `json:"surfshark"`
|
Surfshark bool `json:"surfshark"`
|
||||||
Torguard bool `json:"torguard"`
|
Torguard bool `json:"torguard"`
|
||||||
@@ -49,6 +50,8 @@ func (settings *Updater) read(r reader) (err error) {
|
|||||||
settings.Nordvpn = true
|
settings.Nordvpn = true
|
||||||
settings.Privado = true
|
settings.Privado = true
|
||||||
settings.PIA = true
|
settings.PIA = true
|
||||||
|
settings.Privado = true
|
||||||
|
settings.Privatevpn = true
|
||||||
settings.Purevpn = true
|
settings.Purevpn = true
|
||||||
settings.Surfshark = true
|
settings.Surfshark = true
|
||||||
settings.Torguard = true
|
settings.Torguard = true
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
// Package constants defines constants shared throughout the program.
|
// Package constants defines constants shared throughout the program.
|
||||||
// It also defines constant maps and slices using functions.
|
// It also defines constant maps and slices using functions.
|
||||||
package constants
|
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",
|
"ua": "Ukraine",
|
||||||
"ae": "United Arab Emirates",
|
"ae": "United Arab Emirates",
|
||||||
"gb": "United Kingdom",
|
"gb": "United Kingdom",
|
||||||
|
"uk": "United Kingdom",
|
||||||
"um": "United States Minor Outlying Islands",
|
"um": "United States Minor Outlying Islands",
|
||||||
"us": "United States",
|
"us": "United States",
|
||||||
"uy": "Uruguay",
|
"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,
|
Timestamp: 1612031135,
|
||||||
Servers: PrivadoServers(),
|
Servers: PrivadoServers(),
|
||||||
},
|
},
|
||||||
|
Privatevpn: models.PrivatevpnServers{
|
||||||
|
Version: 1,
|
||||||
|
Timestamp: 1613861528,
|
||||||
|
Servers: PrivatevpnServers(),
|
||||||
|
},
|
||||||
Pia: models.PiaServers{
|
Pia: models.PiaServers{
|
||||||
Version: 4,
|
Version: 4,
|
||||||
Timestamp: 1613480675,
|
Timestamp: 1613480675,
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ func Test_versions(t *testing.T) {
|
|||||||
version: allServers.Pia.Version,
|
version: allServers.Pia.Version,
|
||||||
digest: "3e6066ec",
|
digest: "3e6066ec",
|
||||||
},
|
},
|
||||||
|
"Privatevpn": {
|
||||||
|
model: models.PrivatevpnServer{},
|
||||||
|
version: allServers.Privatevpn.Version,
|
||||||
|
digest: "cba13d78",
|
||||||
|
},
|
||||||
"Purevpn": {
|
"Purevpn": {
|
||||||
model: models.PurevpnServer{},
|
model: models.PurevpnServer{},
|
||||||
version: allServers.Purevpn.Version,
|
version: allServers.Purevpn.Version,
|
||||||
@@ -155,6 +160,11 @@ func Test_timestamps(t *testing.T) {
|
|||||||
timestamp: allServers.Pia.Timestamp,
|
timestamp: allServers.Pia.Timestamp,
|
||||||
digest: "e0f95a01",
|
digest: "e0f95a01",
|
||||||
},
|
},
|
||||||
|
"Privatevpn": {
|
||||||
|
servers: allServers.Privatevpn.Servers,
|
||||||
|
timestamp: allServers.Privatevpn.Timestamp,
|
||||||
|
digest: "8ce3fba1",
|
||||||
|
},
|
||||||
"Purevpn": {
|
"Purevpn": {
|
||||||
servers: allServers.Purevpn.Servers,
|
servers: allServers.Purevpn.Servers,
|
||||||
timestamp: allServers.Purevpn.Timestamp,
|
timestamp: allServers.Purevpn.Timestamp,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const (
|
|||||||
Privado = "privado"
|
Privado = "privado"
|
||||||
// PrivateInternetAccess is a VPN provider.
|
// PrivateInternetAccess is a VPN provider.
|
||||||
PrivateInternetAccess = "private internet access"
|
PrivateInternetAccess = "private internet access"
|
||||||
|
// Privatevpn is a VPN provider.
|
||||||
|
Privatevpn = "privatevpn"
|
||||||
// PureVPN is a VPN provider.
|
// PureVPN is a VPN provider.
|
||||||
Purevpn = "purevpn"
|
Purevpn = "purevpn"
|
||||||
// Surfshark is a VPN provider.
|
// Surfshark is a VPN provider.
|
||||||
|
|||||||
@@ -137,6 +137,18 @@ func (s *WindscribeServer) String() string {
|
|||||||
s.Region, s.City, s.Hostname, goStringifyIP(s.IP))
|
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 {
|
func goStringifyIP(ip net.IP) string {
|
||||||
s := fmt.Sprintf("%#v", ip)
|
s := fmt.Sprintf("%#v", ip)
|
||||||
s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}")
|
s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ type AllServers struct {
|
|||||||
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"`
|
||||||
Purevpn PurevpnServers `json:"purevpn"`
|
Purevpn PurevpnServers `json:"purevpn"`
|
||||||
Surfshark SurfsharkServers `json:"surfshark"`
|
Surfshark SurfsharkServers `json:"surfshark"`
|
||||||
Torguard TorguardServers `json:"torguard"`
|
Torguard TorguardServers `json:"torguard"`
|
||||||
@@ -22,6 +23,7 @@ func (a *AllServers) Count() int {
|
|||||||
len(a.Nordvpn.Servers) +
|
len(a.Nordvpn.Servers) +
|
||||||
len(a.Privado.Servers) +
|
len(a.Privado.Servers) +
|
||||||
len(a.Pia.Servers) +
|
len(a.Pia.Servers) +
|
||||||
|
len(a.Privatevpn.Servers) +
|
||||||
len(a.Purevpn.Servers) +
|
len(a.Purevpn.Servers) +
|
||||||
len(a.Surfshark.Servers) +
|
len(a.Surfshark.Servers) +
|
||||||
len(a.Torguard.Servers) +
|
len(a.Torguard.Servers) +
|
||||||
@@ -59,6 +61,11 @@ type PiaServers struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Servers []PIAServer `json:"servers"`
|
Servers []PIAServer `json:"servers"`
|
||||||
}
|
}
|
||||||
|
type PrivatevpnServers struct {
|
||||||
|
Version uint16 `json:"version"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Servers []PrivatevpnServer `json:"servers"`
|
||||||
|
}
|
||||||
type PurevpnServers struct {
|
type PurevpnServers struct {
|
||||||
Version uint16 `json:"version"`
|
Version uint16 `json:"version"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package provider
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
aes256cbc = "aes-256-cbc"
|
aes256cbc = "aes-256-cbc"
|
||||||
|
aes128gcm = "aes-128-gcm"
|
||||||
aes256gcm = "aes-256-gcm"
|
aes256gcm = "aes-256-gcm"
|
||||||
sha256 = "sha256"
|
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)
|
return newPrivado(allServers.Privado.Servers, timeNow)
|
||||||
case constants.PrivateInternetAccess:
|
case constants.PrivateInternetAccess:
|
||||||
return newPrivateInternetAccess(allServers.Pia.Servers, timeNow)
|
return newPrivateInternetAccess(allServers.Pia.Servers, timeNow)
|
||||||
|
case constants.Privatevpn:
|
||||||
|
return newPrivatevpn(allServers.Privatevpn.Servers, timeNow)
|
||||||
case constants.Purevpn:
|
case constants.Purevpn:
|
||||||
return newPurevpn(allServers.Purevpn.Servers, timeNow)
|
return newPurevpn(allServers.Purevpn.Servers, timeNow)
|
||||||
case constants.Surfshark:
|
case constants.Surfshark:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
|
|||||||
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),
|
||||||
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),
|
||||||
@@ -106,6 +107,22 @@ func (s *storage) mergePIA(hardcoded, persisted models.PiaServers) models.PiaSer
|
|||||||
return persisted
|
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 {
|
func (s *storage) mergePureVPN(hardcoded, persisted models.PurevpnServers) models.PurevpnServers {
|
||||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||||
return hardcoded
|
return hardcoded
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func countServers(allServers models.AllServers) int {
|
|||||||
len(allServers.Nordvpn.Servers) +
|
len(allServers.Nordvpn.Servers) +
|
||||||
len(allServers.Privado.Servers) +
|
len(allServers.Privado.Servers) +
|
||||||
len(allServers.Pia.Servers) +
|
len(allServers.Pia.Servers) +
|
||||||
|
len(allServers.Privatevpn.Servers) +
|
||||||
len(allServers.Purevpn.Servers) +
|
len(allServers.Purevpn.Servers) +
|
||||||
len(allServers.Surfshark.Servers) +
|
len(allServers.Surfshark.Servers) +
|
||||||
len(allServers.Torguard.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 {
|
if u.options.Purevpn {
|
||||||
u.logger.Info("updating PureVPN servers...")
|
u.logger.Info("updating PureVPN servers...")
|
||||||
// TODO support servers offering only TCP or only UDP
|
// TODO support servers offering only TCP or only UDP
|
||||||
|
|||||||
Reference in New Issue
Block a user