Feat: Perfect privacy support (#606)

This commit is contained in:
Quentin McGaw
2021-10-05 10:44:15 -07:00
committed by GitHub
parent e7c952cbf7
commit e0e3ca3832
38 changed files with 1142 additions and 188 deletions

3
.github/labels.yml vendored
View File

@@ -39,6 +39,9 @@
- name: ":cloud: NordVPN" - name: ":cloud: NordVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: Perfect Privacy"
color: "cfe8d4"
description: ""
- name: ":cloud: PIA" - name: ":cloud: PIA"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -1,7 +1,7 @@
# Gluetun VPN client # Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN, *Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN, HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
@@ -62,7 +62,7 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
## Features ## Features
- Based on Alpine 3.14 for a small Docker image of 31MB - Based on Alpine 3.14 for a small Docker image of 31MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers - Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed - Supports OpenVPN for all providers listed
- Supports Wireguard - Supports Wireguard
- For **Mullvad**, **Ivpn** and **Windscribe** - For **Mullvad**, **Ivpn** and **Windscribe**

View File

@@ -26,7 +26,7 @@ var (
func (c *CLI) FormatServers(args []string) error { func (c *CLI) FormatServers(args []string) error {
var format, output string var format, output string
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad, var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
nordvpn, pia, privado, privatevpn, protonvpn, purevpn, surfshark, nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError) flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'") flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
@@ -39,6 +39,7 @@ func (c *CLI) FormatServers(args []string) error {
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers") flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers") flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers") flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers") flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers") flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers") flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
@@ -83,6 +84,8 @@ func (c *CLI) FormatServers(args []string) error {
formatted = currentServers.Mullvad.ToMarkdown() formatted = currentServers.Mullvad.ToMarkdown()
case nordvpn: case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown() formatted = currentServers.Nordvpn.ToMarkdown()
case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown()
case pia: case pia:
formatted = currentServers.Pia.ToMarkdown() formatted = currentServers.Pia.ToMarkdown()
case privado: case privado:

View File

@@ -51,6 +51,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers") flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers")
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers") flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers") flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
flagSet.BoolVar(&options.Perfectprivacy, "perfectprivacy", false, "Update Perfect Privacy 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.Privatevpn, "privatevpn", false, "Update Private VPN servers")

View File

@@ -0,0 +1,43 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readPerfectPrivacy(r reader) (err error) {
settings.Name = constants.Perfectprivacy
servers := r.servers.GetPerfectprivacy()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PerfectprivacyCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
return settings.ServerSelection.OpenVPN.readPerfectPrivacy(r)
}
func (settings *OpenVPNSelection) readPerfectPrivacy(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
portValidation := openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{44, 443, 4433},
allowedUDP: []uint16{44, 443, 4433},
}
settings.CustomPort, err = readOpenVPNCustomPort(r, portValidation)
if err != nil {
return err
}
return nil
}

View File

@@ -67,6 +67,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
err = settings.readMullvad(r) err = settings.readMullvad(r)
case constants.Nordvpn: case constants.Nordvpn:
err = settings.readNordvpn(r) err = settings.readNordvpn(r)
case constants.Perfectprivacy:
err = settings.readPerfectPrivacy(r)
case constants.Privado: case constants.Privado:
err = settings.readPrivado(r) err = settings.readPrivado(r)
case constants.PrivateInternetAccess: case constants.PrivateInternetAccess:
@@ -108,7 +110,7 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
constants.Custom, constants.Custom,
"cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish", "cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish",
"ivpn", "mullvad", "nordvpn", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn", constants.Perfectprivacy, "privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
constants.Wevpn, "windscribe"} constants.Wevpn, "windscribe"}
case constants.Wireguard: case constants.Wireguard:

View File

@@ -168,6 +168,21 @@ func Test_Provider_lines(t *testing.T) {
" |--Protocol: udp", " |--Protocol: udp",
}, },
}, },
"perfectprivacy": {
settings: Provider{
Name: constants.Perfectprivacy,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Cities: []string{"a", "b"},
},
},
lines: []string{
"|--Perfect Privacy settings:",
" |--Cities: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"privado": { "privado": {
settings: Provider{ settings: Provider{
Name: constants.Privado, Name: constants.Privado,

View File

@@ -17,7 +17,8 @@ type ServerSelection struct { //nolint:maligned
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited // Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
Countries []string `json:"countries"` Countries []string `json:"countries"`
// Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, WeVPN, Windscribe // Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, Perfectprivacy, PrivateVPN, Protonvpn,
// PureVPN, VPNUnlimited, WeVPN, Windscribe
Cities []string `json:"cities"` Cities []string `json:"cities"`
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN // Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
Hostnames []string `json:"hostnames"` Hostnames []string `json:"hostnames"`

View File

@@ -19,6 +19,7 @@ type Updater struct {
Ivpn bool `json:"ivpn"` Ivpn bool `json:"ivpn"`
Mullvad bool `json:"mullvad"` Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"` Nordvpn bool `json:"nordvpn"`
Perfectprivacy bool `json:"perfectprivacy"`
PIA bool `json:"pia"` PIA bool `json:"pia"`
Privado bool `json:"privado"` Privado bool `json:"privado"`
Privatevpn bool `json:"privatevpn"` Privatevpn bool `json:"privatevpn"`
@@ -57,6 +58,7 @@ func (settings *Updater) EnableAll() {
settings.Ivpn = true settings.Ivpn = true
settings.Mullvad = true settings.Mullvad = true
settings.Nordvpn = true settings.Nordvpn = true
settings.Perfectprivacy = true
settings.Privado = true settings.Privado = true
settings.PIA = true settings.PIA = true
settings.Privado = true settings.Privado = true

View File

@@ -0,0 +1,21 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
PerfectprivacyCA = "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"
PerfectprivacyCert = "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"
PerfectprivacyPrivateKey = "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC14KEVZ06e6ciRusQf+pluWNeKi8PGnveFC+BGrcbIOln49itmDrKSmWMBZQKI9wd9v6YaIEL58QusmPOlXTF42VZtlLxs48S3VtCm/jCIHgWBdwUn2XPEvAU1iJtuE4KtVcMXgyXd/Q56d79d+ZlsKvfUb1NnXDo0uhtRknH9mcfKA14h9IgUAXR7mXiwSmU+0Qs58KdcfbIA/wqcUA6cRnQEqNNvIbVV24//Au+shfYoNMfzYVgrrWUcF99glOvAkUQ+Fl5WcmZJtC2+ai9kHEKr9uT4dE7/xmx0KZKlE/TpBKHoTXTokH5gDs/AKrK/GwNRS43OmaB92uOB7Diw2eLKvs6OYO213/FeQHOhH3GJ6xQid4H2hQyAiJfz1b4BHtl9HP+ihA+wuvZG82LWFWUbYbHU+/g8COg2eCFQES+kxXiaKo9mo22bhyI5hOT1tF6B6SjwLWS2CtxyJTsX5+J4irJKwX2UG/nS8Mnls9SqU36ldAYIGSMgBBtC8Jd66PLGhXLYGgfgrXrID9wr4/KbqqKfwojlq8RSAeunDYvHkK8ZtHdu32O1gUM2ckSVuQYDka6GEeUNnb2xfnWxaVZrA/NHhG2SJXzdn94dSGXicaTJBVonJv4oTTUYfsZFFlBQVn4gj+Yl7ICNiMWDbK92Ekf1fCA6ApsnNP1H9QIDAQABAoICAQCfmpLhPHny3EclE1delMQl4JKtQv83gnLFb2mNvJuPRB2Ga0gkVEuCeFY4eBKkbNtHD3JMxPjhaxUKjmJpQAHVAixlFzvO9oW/OdD6al/eYzIDrZV5pcqA31pW4x06mKZ5Q6RjMrR9PL+C2yi05/8pu/8ljdgMARQXByZIDBI6MMPxU8k8VOFBZRF6EXCmi3KTkFCgtL25XZhiZW1DRMG9g9n16M06XcNKp9WSPFpk9F3SZJb+zfLYyV3MLGraz3Se1RukvG5mwBdhIFtwGLCj0mTzkULXgQF+VPsBaSYF9SBbh7QpLiekmoA7/WN0SEP5jlP3+CxmG8yKBRbXAZuwoTcG1wP14wKbjfV9g/dkm2cjJkHpvLjMV9v52s9ajSDYes+gEyN9P8T0tKQ+0zARuXQMVJHOdTgHA6duFdtrZr1HM/QothyZqh5fA+zkZwCX9LPNKZjqetkESEPBU518q9k8eHiXFHkewcAzSE/K7ILQA8RhapUL1wble5bbtTcFb7W1C4L+YE0KArPloynpgwb8C3tC2KTyoz7B7/52/ERBhd3giBOR6sHnTyuBywF3XtPyHqEt9rMg6tgii9qlj+uG/ZpHBqvQyhRzIFYn6SYhKzUg+C0CYyY83+1NHVBAKIxj6b/OZi/hV20uasaXDG6Vo8zHbAalHml4sBScwQKCAQEA8XoDn4ul6+s0f4Wz8GykDjKze8UooGKK4R6fyE3e+y2+5y6v2UwerJxAMSkG6E5dS53qjVlJgWk6U2hAk8THrQiPdPLOw/jNbRkOKRSpG5HkYsp0lkgF9pXx1ULBydDeK6CbaCJEx6A+NdlW3PE1oDZJvE8U9H7M+ax4wx8xRjgz4u/iIUHZGRdJDWuy4qx8JT8Lw644G8Hays0zmN8sqB6keENyZSjJcvoa9KrGT8HippHplhLlLOe/gxZu50Z2IIcf1WVtNinDYFGBCc6fRhGf7jP3pLqrK7iUWPq6e3YFsfYuG9KnVBamd4Ut7O6BsRPktjQyT6SbriwI5GVa5QKCAQEAwND5YJpr5VYyViEAStVAWZpT4VVS1A2rFil9xuTrSiSeV/KdkdCiQsMzY0e9VS1ZxODRZjcQ+m36EUZ1oV8Ky+Rk0GqaV2z5pHlAtbjhdQXHjxZ40SPV8sITPKWJIoTbRJadlbRlqCxL6SZHuALIbIxg1lbi+PDBrn13TnYz/CF9fJJWTLuVhhlp+mK0KN/8rWorG9pVADgGUv9dxGI17n2IQW7n287spu9SNd8Esyn8x7yWSbR9rjzgSkTYFpkpjD4Pj21S+zZ6d5GDnuraL4ulbeGxl/iHzthFayl/HVxpNouoNXsXa5lD21nieArUcMc0LIonOhzPIkT8eA2X0QKCAQEA2aBVU5zP1GcN0T/2g1/mGsWm7I0rqCAneevXpPZJV6ZKvp9c7EGmA3puf9+x0fuOKXAQy4MEtBTZ9AGo8YQPUOq+H3AU2JmKyiAimvN71NUPN9muaSJP/YP1h3W6oOAU2szMQnVf92l7p5xQpJ7e7Zz/py6+e/srUHkX/QJHrjlIyeXXrpFhzzMlK2s8tP0uhYLkX17MQnfbb5qwPb4kyP+Uyq4+ktzHcU/mq0qdn5PlaKloE1DEKkxSVRoKqXTfUUF2dyQJ4R6SbmQGH4iQEt4ffNZpAZUaXzTiva56Enqzd0efFoQrOaWQMXddhIMPbz+2iF9SWGTJyZb2DKEr4QKCAQAi7/qv0WtJg+PdDV/DL37YfYlDZDV87PkaK+x5dJNZvObgIrsAZ+Bu3nXaQG6DF5OTg/UNY171MaZFKRI5akJHjZvi094hh0J41euuwdBAZwqw166OnsKumRHpRElj8tTUScJGFQjyfwxGM8R9CCwO1yTY0aeQ2fcOSferROnIfr0BLHbssnS2drZoQyhH28YqGfmzs00BnCUxNspjwjPpgd+Fk7X5czYYTXcFAeMVH7+I5ZgJxOWdA7TUYEMTXS9VFQ22vGVz1Xw9XCWQTxe308Lm9SU71zGsfi2d7Ef3Jv59frK89g/ZVE0iWtgZTkUOJlpC08ml0wCJQhzJGBVxAoIBAEk100VOKOQt3IoW7AfxQ5Ac2VPoNVFOYzypO/aywrGvSdG1eIR1EvEmL7OwgZg4qEkezm60F6RMkh985G6V94RVVOyTuS0bBz9tzlD440RMU8sWOi66xSz+yScs/aBHd31dna9zzen16vBH3tx+BGTb9CJQWrVK9+kwPOKNlmRqAGuEALf4BoF/Obwoyva6NxVoTBV0BnKQr5DofpKgDLjlNeDDRFaOXN/YNhviqBNg8JziauEwIom7ysIaYVLugu6xXJd3zhBcwlIDJ6LW8wh7nc8O+n3Igmsl2zTmzN5xFDNWwOQmJVjzTDBzR8vLOtRZNZpMP0pk54iKNog36zY="
PerfectprivacyTLSCryptOpenvpnStaticKeyV1 = "d10a8e2641f5834f6c5e04a6ee9a798553d338fa2836ef2a91057c1f6174a3a12b36f16d1110b20e42ae94d3bd579213e9c3770be6c74804348dddba876945a5a3ab7660f9436f85f331641f6efc81315f0d12b2766a9f15c10a53cf9ba32dc80f03b5f15a6cc6987bda795dbe83443ec81f3d5e161cd47fab6b1f125b3adeee1eae33370d018594e0ff6b25b815228d27371b32c82a95f4929d3abb5fa36e57bf1f42353542568fbb8233f4645f05820275f79570cb8bbcf8010fc5d20f07d031a8227d45daf7349e34158c91a3d4e5add19cfa02f683f87609f6525fa0594016d11abf2de649f83ad54edd3e74e032e34b1bca685b8499916826d9aee11c13"
)
func PerfectprivacyCityChoices(servers []models.PerfectprivacyServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}

View File

@@ -25,6 +25,8 @@ const (
Mullvad = "mullvad" Mullvad = "mullvad"
// Nordvpn is a VPN provider. // Nordvpn is a VPN provider.
Nordvpn = "nordvpn" Nordvpn = "nordvpn"
// Perfectprivacy is a VPN provider.
Perfectprivacy = "perfect privacy"
// Privado is a VPN provider. // Privado is a VPN provider.
Privado = "privado" Privado = "privado"
// PrivateInternetAccess is a VPN provider. // PrivateInternetAccess is a VPN provider.

View File

@@ -14,6 +14,7 @@ func (a AllServers) GetCopy() (servers AllServers) {
servers.Ivpn.Servers = a.GetIvpn() servers.Ivpn.Servers = a.GetIvpn()
servers.Mullvad.Servers = a.GetMullvad() servers.Mullvad.Servers = a.GetMullvad()
servers.Nordvpn.Servers = a.GetNordvpn() servers.Nordvpn.Servers = a.GetNordvpn()
servers.Perfectprivacy.Servers = a.GetPerfectprivacy()
servers.Privado.Servers = a.GetPrivado() servers.Privado.Servers = a.GetPrivado()
servers.Pia.Servers = a.GetPia() servers.Pia.Servers = a.GetPia()
servers.Privatevpn.Servers = a.GetPrivatevpn() servers.Privatevpn.Servers = a.GetPrivatevpn()
@@ -124,6 +125,18 @@ func (a *AllServers) GetNordvpn() (servers []NordvpnServer) {
return servers return servers
} }
func (a *AllServers) GetPerfectprivacy() (servers []PerfectprivacyServer) {
if a.Perfectprivacy.Servers == nil {
return nil
}
servers = make([]PerfectprivacyServer, len(a.Perfectprivacy.Servers))
for i, serverToCopy := range a.Perfectprivacy.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetPia() (servers []PIAServer) { func (a *AllServers) GetPia() (servers []PIAServer) {
if a.Pia.Servers == nil { if a.Pia.Servers == nil {
return nil return nil

View File

@@ -51,6 +51,11 @@ func Test_AllServers_GetCopy(t *testing.T) {
IP: net.IP{1, 2, 3, 4}, IP: net.IP{1, 2, 3, 4},
}}, }},
}, },
Perfectprivacy: PerfectprivacyServers{
Servers: []PerfectprivacyServer{{
IPs: []net.IP{{1, 2, 3, 4}},
}},
},
Privado: PrivadoServers{ Privado: PrivadoServers{
Servers: []PrivadoServer{{ Servers: []PrivadoServer{{
IP: net.IP{1, 2, 3, 4}, IP: net.IP{1, 2, 3, 4},

View File

@@ -141,6 +141,19 @@ func (s *PrivadoServer) ToMarkdown() (markdown string) {
s.Country, s.Region, s.City, s.Hostname) s.Country, s.Region, s.City, s.Hostname)
} }
func (s *PerfectprivacyServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("City", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PerfectprivacyServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s |",
s.City, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PiaServers) ToMarkdown() (markdown string) { func (s *PiaServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP") markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers { for _, server := range s.Servers {

View File

@@ -81,6 +81,13 @@ type NordvpnServer struct { //nolint:maligned
UDP bool `json:"udp"` UDP bool `json:"udp"`
} }
type PerfectprivacyServer struct {
City string `json:"city"` // primary key
IPs []net.IP `json:"ips"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
}
type PrivadoServer struct { type PrivadoServer struct {
Country string `json:"country"` Country string `json:"country"`
Region string `json:"region"` Region string `json:"region"`

View File

@@ -9,6 +9,7 @@ type AllServers struct {
Ipvanish IpvanishServers `json:"ipvanish"` Ipvanish IpvanishServers `json:"ipvanish"`
Ivpn IvpnServers `json:"ivpn"` Ivpn IvpnServers `json:"ivpn"`
Mullvad MullvadServers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"`
Nordvpn NordvpnServers `json:"nordvpn"` Nordvpn NordvpnServers `json:"nordvpn"`
Privado PrivadoServers `json:"privado"` Privado PrivadoServers `json:"privado"`
Pia PiaServers `json:"pia"` Pia PiaServers `json:"pia"`
@@ -32,6 +33,7 @@ func (a *AllServers) Count() int {
len(a.Ivpn.Servers) + len(a.Ivpn.Servers) +
len(a.Mullvad.Servers) + len(a.Mullvad.Servers) +
len(a.Nordvpn.Servers) + len(a.Nordvpn.Servers) +
len(a.Perfectprivacy.Servers) +
len(a.Privado.Servers) + len(a.Privado.Servers) +
len(a.Pia.Servers) + len(a.Pia.Servers) +
len(a.Privatevpn.Servers) + len(a.Privatevpn.Servers) +
@@ -85,6 +87,11 @@ type NordvpnServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []NordvpnServer `json:"servers"` Servers []NordvpnServer `json:"servers"`
} }
type PerfectprivacyServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PerfectprivacyServer `json:"servers"`
}
type PrivadoServers struct { type PrivadoServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -11,8 +11,6 @@ var (
) )
func extractPEM(b []byte, name string) (encodedData string, err error) { func extractPEM(b []byte, name string) (encodedData string, err error) {
name = strings.ToUpper(name) // certificate -> CERTIFICATE
pemBlock, _ := pem.Decode(b) pemBlock, _ := pem.Decode(b)
if pemBlock == nil { if pemBlock == nil {
return "", errPEMDecode return "", errPEMDecode

View File

@@ -25,7 +25,7 @@ func Test_extractPEM(t *testing.T) {
err: errors.New("cannot decode PEM encoded block"), err: errors.New("cannot decode PEM encoded block"),
}, },
"valid data": { "valid data": {
name: "certificate", name: "CERTIFICATE",
b: []byte(validCertPEM), b: []byte(validCertPEM),
encodedData: validCertData, encodedData: validCertData,
}, },

View File

@@ -0,0 +1,37 @@
package perfectprivacy
import (
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (p *Perfectprivacy) GetConnection(selection configuration.ServerSelection) (
connection models.Connection, err error) {
const defaultPort uint16 = 443
port := defaultPort
if selection.OpenVPN.CustomPort > 0 {
port = selection.OpenVPN.CustomPort
}
protocol := utils.GetProtocol(selection)
servers, err := p.filterServers(selection)
if err != nil {
return connection, err
}
var connections []models.Connection
for _, server := range servers {
for _, IP := range server.IPs {
connection := models.Connection{
Type: selection.VPN,
IP: IP,
Port: port,
Protocol: protocol,
}
connections = append(connections, connection)
}
}
return utils.PickConnection(connections, selection, p.randSource)
}

View File

@@ -0,0 +1,26 @@
package perfectprivacy
import (
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (p *Perfectprivacy) filterServers(selection configuration.ServerSelection) (
servers []models.PerfectprivacyServer, err error) {
for _, server := range p.servers {
switch {
case
utils.FilterByPossibilities(server.City, selection.Cities),
utils.FilterByProtocol(selection, server.TCP, server.UDP):
default:
servers = append(servers, server)
}
}
if len(servers) == 0 {
return nil, utils.NoServerFoundError(selection)
}
return servers, nil
}

View File

@@ -0,0 +1,89 @@
package perfectprivacy
import (
"strconv"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (p *Perfectprivacy) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" {
// TODO add AES 256 GCM
settings.Cipher = constants.AES256cbc
}
if settings.Auth == "" {
settings.Auth = constants.SHA512
}
if settings.MSSFix == 0 {
settings.MSSFix = 1450
}
lines = []string{
"client",
"nobind",
"tls-exit",
"dev " + settings.Interface,
"verb " + strconv.Itoa(settings.Verbosity),
// Perfect Privacy specific
"ping 5",
"tun-mtu 1500",
"tun-mtu-extra 32",
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
"reneg-sec 3600",
"key-direction 1",
"tls-cipher TLS_CHACHA20_POLY1305_SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS_AES_256_GCM_SHA384:TLS-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
"auth-user-pass " + constants.OpenVPNAuthConf,
"auth " + settings.Auth,
// Added constant values
"auth-nocache",
"mute-replay-warnings",
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
"auth-retry nointeract",
"suppress-timestamps",
// Modified variables
connection.OpenVPNProtoLine(),
connection.OpenVPNRemoteLine(),
}
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")
}
if !settings.Root {
lines = append(lines, "user "+settings.ProcUser)
lines = append(lines, "persist-tun")
lines = append(lines, "persist-key")
}
if !settings.IPv6 {
lines = append(lines, `pull-filter ignore "route-ipv6"`)
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
// Perfect Privacy specific IPv6
lines = append(lines, "redirect-gateway def1")
lines = append(lines, `pull-filter ignore "redirect-gateway def1 ipv6"`)
}
lines = append(lines, utils.WrapOpenvpnCA(
constants.PerfectprivacyCA)...)
lines = append(lines, utils.WrapOpenvpnCert(
constants.PerfectprivacyCert)...)
lines = append(lines, utils.WrapOpenvpnKey(
constants.PerfectprivacyPrivateKey)...)
lines = append(lines, utils.WrapOpenvpnTLSCrypt(
constants.PerfectprivacyTLSCryptOpenvpnStaticKeyV1)...)
lines = append(lines, "")
return lines, nil
}

View File

@@ -0,0 +1,23 @@
package perfectprivacy
import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Perfectprivacy struct {
servers []models.PerfectprivacyServer
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.PerfectprivacyServer, randSource rand.Source) *Perfectprivacy {
return &Perfectprivacy{
servers: servers,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(constants.Perfectprivacy),
}
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/qdm12/gluetun/internal/provider/ivpn" "github.com/qdm12/gluetun/internal/provider/ivpn"
"github.com/qdm12/gluetun/internal/provider/mullvad" "github.com/qdm12/gluetun/internal/provider/mullvad"
"github.com/qdm12/gluetun/internal/provider/nordvpn" "github.com/qdm12/gluetun/internal/provider/nordvpn"
"github.com/qdm12/gluetun/internal/provider/perfectprivacy"
"github.com/qdm12/gluetun/internal/provider/privado" "github.com/qdm12/gluetun/internal/provider/privado"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
"github.com/qdm12/gluetun/internal/provider/privatevpn" "github.com/qdm12/gluetun/internal/provider/privatevpn"
@@ -70,6 +71,8 @@ func New(provider string, allServers models.AllServers, timeNow func() time.Time
return mullvad.New(allServers.Mullvad.Servers, randSource) return mullvad.New(allServers.Mullvad.Servers, randSource)
case constants.Nordvpn: case constants.Nordvpn:
return nordvpn.New(allServers.Nordvpn.Servers, randSource) return nordvpn.New(allServers.Nordvpn.Servers, randSource)
case constants.Perfectprivacy:
return perfectprivacy.New(allServers.Perfectprivacy.Servers, randSource)
case constants.Privado: case constants.Privado:
return privado.New(allServers.Privado.Servers, randSource) return privado.New(allServers.Privado.Servers, randSource)
case constants.PrivateInternetAccess: case constants.PrivateInternetAccess:

View File

@@ -88,6 +88,11 @@ func Test_versions(t *testing.T) {
version: allServers.Nordvpn.Version, version: allServers.Nordvpn.Version,
digest: "a8043704", digest: "a8043704",
}, },
"Perfect privacy": {
model: models.PerfectprivacyServer{},
version: allServers.Perfectprivacy.Version,
digest: "233f0dd4",
},
"Privado": { "Privado": {
model: models.PrivadoServer{}, model: models.PrivadoServer{},
version: allServers.Privado.Version, version: allServers.Privado.Version,

View File

@@ -37,6 +37,7 @@ func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn), Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
Perfectprivacy: s.mergePerfectprivacy(hardcoded.Perfectprivacy, persisted.Perfectprivacy),
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia), Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn), Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
@@ -123,6 +124,15 @@ func (s *Storage) mergeNordVPN(hardcoded, persisted models.NordvpnServers) model
return persisted return persisted
} }
func (s *Storage) mergePerfectprivacy(hardcoded, persisted models.PerfectprivacyServers) models.PerfectprivacyServers {
if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded
}
s.logTimeDiff("Perfect Privacy", persisted.Timestamp, hardcoded.Timestamp)
return persisted
}
func (s *Storage) mergePrivado(hardcoded, persisted models.PrivadoServers) models.PrivadoServers { func (s *Storage) mergePrivado(hardcoded, persisted models.PrivadoServers) models.PrivadoServers {
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded return hardcoded

View File

@@ -126,6 +126,15 @@ func (s *Storage) extractServersFromBytes(b []byte, hardcoded models.AllServers)
} }
} }
if hardcoded.Perfectprivacy.Version != versions.Perfectprivacy.Version {
s.logVersionDiff("Perfect Privacy", hardcoded.Perfectprivacy.Version, versions.Perfectprivacy.Version)
} else {
err = json.Unmarshal(rawMessages.Perfectprivacy, &servers.Perfectprivacy)
if err != nil {
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Perfect Privacy", err)
}
}
if hardcoded.Privado.Version != versions.Privado.Version { if hardcoded.Privado.Version != versions.Privado.Version {
s.logVersionDiff("Privado", hardcoded.Privado.Version, versions.Privado.Version) s.logVersionDiff("Privado", hardcoded.Privado.Version, versions.Privado.Version)
} else { } else {
@@ -240,6 +249,7 @@ type allVersions struct {
Ivpn serverVersion `json:"ivpn"` Ivpn serverVersion `json:"ivpn"`
Mullvad serverVersion `json:"mullvad"` Mullvad serverVersion `json:"mullvad"`
Nordvpn serverVersion `json:"nordvpn"` Nordvpn serverVersion `json:"nordvpn"`
Perfectprivacy serverVersion `json:"perfectprivacy"`
Privado serverVersion `json:"privado"` Privado serverVersion `json:"privado"`
Pia serverVersion `json:"pia"` Pia serverVersion `json:"pia"`
Privatevpn serverVersion `json:"privatevpn"` Privatevpn serverVersion `json:"privatevpn"`
@@ -268,6 +278,7 @@ type allJSONRawMessages struct {
Ivpn json.RawMessage `json:"ivpn"` Ivpn json.RawMessage `json:"ivpn"`
Mullvad json.RawMessage `json:"mullvad"` Mullvad json.RawMessage `json:"mullvad"`
Nordvpn json.RawMessage `json:"nordvpn"` Nordvpn json.RawMessage `json:"nordvpn"`
Perfectprivacy json.RawMessage `json:"perfectprivacy"`
Privado json.RawMessage `json:"privado"` Privado json.RawMessage `json:"privado"`
Pia json.RawMessage `json:"pia"` Pia json.RawMessage `json:"pia"`
Privatevpn json.RawMessage `json:"privatevpn"` Privatevpn json.RawMessage `json:"privatevpn"`

View File

@@ -38,6 +38,7 @@ func Test_extractServersFromBytes(t *testing.T) {
Ivpn: models.IvpnServers{Version: 1}, Ivpn: models.IvpnServers{Version: 1},
Mullvad: models.MullvadServers{Version: 1}, Mullvad: models.MullvadServers{Version: 1},
Nordvpn: models.NordvpnServers{Version: 1}, Nordvpn: models.NordvpnServers{Version: 1},
Perfectprivacy: models.PerfectprivacyServers{Version: 1},
Privado: models.PrivadoServers{Version: 1}, Privado: models.PrivadoServers{Version: 1},
Pia: models.PiaServers{Version: 1}, Pia: models.PiaServers{Version: 1},
Privatevpn: models.PrivatevpnServers{Version: 1}, Privatevpn: models.PrivatevpnServers{Version: 1},
@@ -59,6 +60,7 @@ func Test_extractServersFromBytes(t *testing.T) {
"Ivpn servers from file discarded because they have version 0 and hardcoded servers have version 1", "Ivpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
"Mullvad servers from file discarded because they have version 0 and hardcoded servers have version 1", "Mullvad servers from file discarded because they have version 0 and hardcoded servers have version 1",
"Nordvpn servers from file discarded because they have version 0 and hardcoded servers have version 1", "Nordvpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
"Perfect Privacy servers from file discarded because they have version 0 and hardcoded servers have version 1",
"Privado servers from file discarded because they have version 0 and hardcoded servers have version 1", "Privado servers from file discarded because they have version 0 and hardcoded servers have version 1",
"Pia servers from file discarded because they have version 0 and hardcoded servers have version 1", "Pia servers from file discarded because they have version 0 and hardcoded servers have version 1",
"Privatevpn servers from file discarded because they have version 0 and hardcoded servers have version 1", "Privatevpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
@@ -82,6 +84,7 @@ func Test_extractServersFromBytes(t *testing.T) {
"ivpn": {"version": 1, "timestamp": 1}, "ivpn": {"version": 1, "timestamp": 1},
"mullvad": {"version": 1, "timestamp": 1}, "mullvad": {"version": 1, "timestamp": 1},
"nordvpn": {"version": 1, "timestamp": 1}, "nordvpn": {"version": 1, "timestamp": 1},
"perfectprivacy": {"version": 1, "timestamp": 1},
"privado": {"version": 1, "timestamp": 1}, "privado": {"version": 1, "timestamp": 1},
"pia": {"version": 1, "timestamp": 1}, "pia": {"version": 1, "timestamp": 1},
"privatevpn": {"version": 1, "timestamp": 1}, "privatevpn": {"version": 1, "timestamp": 1},
@@ -103,6 +106,7 @@ func Test_extractServersFromBytes(t *testing.T) {
Ivpn: models.IvpnServers{Version: 1}, Ivpn: models.IvpnServers{Version: 1},
Mullvad: models.MullvadServers{Version: 1}, Mullvad: models.MullvadServers{Version: 1},
Nordvpn: models.NordvpnServers{Version: 1}, Nordvpn: models.NordvpnServers{Version: 1},
Perfectprivacy: models.PerfectprivacyServers{Version: 1},
Privado: models.PrivadoServers{Version: 1}, Privado: models.PrivadoServers{Version: 1},
Pia: models.PiaServers{Version: 1}, Pia: models.PiaServers{Version: 1},
Privatevpn: models.PrivatevpnServers{Version: 1}, Privatevpn: models.PrivatevpnServers{Version: 1},
@@ -124,6 +128,7 @@ func Test_extractServersFromBytes(t *testing.T) {
Ivpn: models.IvpnServers{Version: 1, Timestamp: 1}, Ivpn: models.IvpnServers{Version: 1, Timestamp: 1},
Mullvad: models.MullvadServers{Version: 1, Timestamp: 1}, Mullvad: models.MullvadServers{Version: 1, Timestamp: 1},
Nordvpn: models.NordvpnServers{Version: 1, Timestamp: 1}, Nordvpn: models.NordvpnServers{Version: 1, Timestamp: 1},
Perfectprivacy: models.PerfectprivacyServers{Version: 1, Timestamp: 1},
Privado: models.PrivadoServers{Version: 1, Timestamp: 1}, Privado: models.PrivadoServers{Version: 1, Timestamp: 1},
Pia: models.PiaServers{Version: 1, Timestamp: 1}, Pia: models.PiaServers{Version: 1, Timestamp: 1},
Privatevpn: models.PrivatevpnServers{Version: 1, Timestamp: 1}, Privatevpn: models.PrivatevpnServers{Version: 1, Timestamp: 1},

View File

@@ -39835,6 +39835,421 @@
} }
] ]
}, },
"perfectprivacy": {
"version": 1,
"timestamp": 1633357036,
"servers": [
{
"city": "Amsterdam",
"ips": [
"95.211.95.244",
"95.211.95.244",
"95.211.95.244",
"37.48.94.1",
"85.17.64.131",
"95.211.95.232",
"85.17.28.145"
],
"tcp": true,
"udp": true
},
{
"city": "Basel",
"ips": [
"82.199.134.162",
"82.199.134.162",
"82.199.134.162",
"80.255.7.66"
],
"tcp": true,
"udp": true
},
{
"city": "Belgrade",
"ips": [
"152.89.160.98",
"152.89.160.98",
"152.89.160.98"
],
"tcp": true,
"udp": true
},
{
"city": "Berlin",
"ips": [
"80.255.7.98",
"80.255.7.98",
"80.255.7.98"
],
"tcp": true,
"udp": true
},
{
"city": "Bucharest",
"ips": [
"185.57.82.25",
"185.57.82.25",
"185.57.82.25"
],
"tcp": true,
"udp": true
},
{
"city": "Calais",
"ips": [
"149.202.77.77",
"149.202.77.77",
"149.202.77.77"
],
"tcp": true,
"udp": true
},
{
"city": "Chicago",
"ips": [
"104.237.193.26",
"104.237.193.26",
"104.237.193.26"
],
"tcp": true,
"udp": true
},
{
"city": "Copenhagen",
"ips": [
"185.152.32.66",
"185.152.32.66",
"185.152.32.66"
],
"tcp": true,
"udp": true
},
{
"city": "Dallas",
"ips": [
"138.128.136.164",
"138.128.136.164",
"138.128.136.164"
],
"tcp": true,
"udp": true
},
{
"city": "Erfurt",
"ips": [
"217.114.218.18",
"217.114.218.18",
"217.114.218.18"
],
"tcp": true,
"udp": true
},
{
"city": "Frankfurt",
"ips": [
"178.162.194.30",
"178.162.194.30",
"178.162.194.30",
"37.58.58.239"
],
"tcp": true,
"udp": true
},
{
"city": "Hamburg",
"ips": [
"80.255.7.114",
"80.255.7.114",
"80.255.7.114"
],
"tcp": true,
"udp": true
},
{
"city": "Hongkong",
"ips": [
"209.58.188.129",
"209.58.188.129",
"209.58.188.129"
],
"tcp": true,
"udp": true
},
{
"city": "Istanbul",
"ips": [
"185.65.205.18",
"185.65.205.18",
"185.65.205.18"
],
"tcp": true,
"udp": true
},
{
"city": "Jerusalem",
"ips": [
"82.81.85.231",
"82.81.85.231",
"82.81.85.231"
],
"tcp": true,
"udp": true
},
{
"city": "London",
"ips": [
"82.199.130.34",
"82.199.130.34",
"82.199.130.34",
"5.187.21.98"
],
"tcp": true,
"udp": true
},
{
"city": "LosAngeles",
"ips": [
"162.245.206.242",
"162.245.206.242",
"162.245.206.242"
],
"tcp": true,
"udp": true
},
{
"city": "Madrid",
"ips": [
"185.183.106.146",
"185.183.106.146",
"185.183.106.146"
],
"tcp": true,
"udp": true
},
{
"city": "Malmoe",
"ips": [
"194.68.170.51",
"194.68.170.51",
"194.68.170.51"
],
"tcp": true,
"udp": true
},
{
"city": "Manchester",
"ips": [
"217.138.196.98",
"217.138.196.98",
"217.138.196.98"
],
"tcp": true,
"udp": true
},
{
"city": "Miami",
"ips": [
"38.132.118.66",
"38.132.118.66",
"38.132.118.66"
],
"tcp": true,
"udp": true
},
{
"city": "Milan",
"ips": [
"192.145.127.210",
"192.145.127.210",
"192.145.127.210"
],
"tcp": true,
"udp": true
},
{
"city": "Montreal",
"ips": [
"167.114.209.103",
"167.114.209.103",
"167.114.209.103"
],
"tcp": true,
"udp": true
},
{
"city": "Moscow",
"ips": [
"192.162.100.241",
"192.162.100.241",
"192.162.100.241",
"192.162.100.240"
],
"tcp": true,
"udp": true
},
{
"city": "NewYork",
"ips": [
"96.9.246.194",
"96.9.246.194",
"96.9.246.194"
],
"tcp": true,
"udp": true
},
{
"city": "Nuremberg",
"ips": [
"81.95.5.34",
"81.95.5.34",
"81.95.5.34",
"80.255.10.194"
],
"tcp": true,
"udp": true
},
{
"city": "Oslo",
"ips": [
"91.205.187.186",
"91.205.187.186",
"91.205.187.186"
],
"tcp": true,
"udp": true
},
{
"city": "Paris",
"ips": [
"5.135.143.84",
"5.135.143.84",
"5.135.143.84"
],
"tcp": true,
"udp": true
},
{
"city": "Prague",
"ips": [
"195.138.249.2",
"195.138.249.2",
"195.138.249.2"
],
"tcp": true,
"udp": true
},
{
"city": "Reykjavik",
"ips": [
"82.221.105.61",
"82.221.105.61",
"82.221.105.61"
],
"tcp": true,
"udp": true
},
{
"city": "Riga",
"ips": [
"46.183.221.194",
"46.183.221.194",
"46.183.221.194"
],
"tcp": true,
"udp": true
},
{
"city": "Rotterdam",
"ips": [
"31.204.152.102",
"31.204.152.102",
"31.204.152.102",
"31.204.152.189",
"31.204.150.106",
"31.204.150.138",
"31.204.153.106"
],
"tcp": true,
"udp": true
},
{
"city": "Singapore",
"ips": [
"103.254.153.202",
"103.254.153.202",
"103.254.153.202",
"209.58.162.197"
],
"tcp": true,
"udp": true
},
{
"city": "Steinsel",
"ips": [
"94.242.243.162",
"94.242.243.162",
"94.242.243.162",
"94.242.243.66"
],
"tcp": true,
"udp": true
},
{
"city": "Stockholm",
"ips": [
"185.41.240.18",
"185.41.240.18",
"185.41.240.18",
"185.217.1.2"
],
"tcp": true,
"udp": true
},
{
"city": "Strasbourg",
"ips": [
"37.187.163.66",
"37.187.163.66",
"37.187.163.66"
],
"tcp": true,
"udp": true
},
{
"city": "Sydney",
"ips": [
"66.203.112.50",
"66.203.112.50",
"66.203.112.50",
"66.203.112.47"
],
"tcp": true,
"udp": true
},
{
"city": "Tokyo",
"ips": [
"31.204.145.166",
"31.204.145.166",
"31.204.145.166"
],
"tcp": true,
"udp": true
},
{
"city": "Zurich",
"ips": [
"37.120.213.210",
"37.120.213.210",
"37.120.213.210",
"37.120.213.194",
"152.89.162.226"
],
"tcp": true,
"udp": true
}
]
},
"nordvpn": { "nordvpn": {
"version": 3, "version": 3,
"timestamp": 1627008323, "timestamp": 1627008323,

View File

@@ -22,6 +22,7 @@ func countServers(allServers models.AllServers) int {
len(allServers.Ivpn.Servers) + len(allServers.Ivpn.Servers) +
len(allServers.Mullvad.Servers) + len(allServers.Mullvad.Servers) +
len(allServers.Nordvpn.Servers) + len(allServers.Nordvpn.Servers) +
len(allServers.Perfectprivacy.Servers) +
len(allServers.Privado.Servers) + len(allServers.Privado.Servers) +
len(allServers.Pia.Servers) + len(allServers.Pia.Servers) +
len(allServers.Privatevpn.Servers) + len(allServers.Privatevpn.Servers) +

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sort"
"strings" "strings"
) )
@@ -53,20 +54,38 @@ func ExtractHost(b []byte) (host, warning string, err error) {
return hosts[0], warning, nil return hosts[0], warning, nil
} }
func ExtractIPs(b []byte) (ips []net.IP, err error) {
const rejectIP, rejectDomain = false, true
ipStrings := extractRemoteHosts(b, rejectIP, rejectDomain)
if len(ipStrings) == 0 {
return nil, ErrNoRemoteIP
}
sort.Slice(ipStrings, func(i, j int) bool {
return ipStrings[i] < ipStrings[j]
})
ips = make([]net.IP, len(ipStrings))
for i := range ipStrings {
ips[i] = net.ParseIP(ipStrings[i])
}
return ips, nil
}
func ExtractIP(b []byte) (ip net.IP, warning string, err error) { func ExtractIP(b []byte) (ip net.IP, warning string, err error) {
const ( ips, err := ExtractIPs(b)
rejectIP = false if err != nil {
rejectDomain = true return nil, "", err
) }
ips := extractRemoteHosts(b, rejectIP, rejectDomain)
if len(ips) == 0 { if len(ips) > 1 {
return nil, "", ErrNoRemoteIP
} else if len(ips) > 1 {
warning = fmt.Sprintf( warning = fmt.Sprintf(
"only using the first IP address %s and discarding %d other hosts", "only using the first IP address %s and discarding %d other hosts",
ips[0], len(ips)-1) ips[0], len(ips)-1)
} }
return net.ParseIP(ips[0]), warning, nil
return ips[0], warning, nil
} }
func extractRemoteHosts(content []byte, rejectIP, rejectDomain bool) (hosts []string) { func extractRemoteHosts(content []byte, rejectIP, rejectDomain bool) (hosts []string) {

View File

@@ -14,6 +14,7 @@ import (
"github.com/qdm12/gluetun/internal/updater/providers/ivpn" "github.com/qdm12/gluetun/internal/updater/providers/ivpn"
"github.com/qdm12/gluetun/internal/updater/providers/mullvad" "github.com/qdm12/gluetun/internal/updater/providers/mullvad"
"github.com/qdm12/gluetun/internal/updater/providers/nordvpn" "github.com/qdm12/gluetun/internal/updater/providers/nordvpn"
"github.com/qdm12/gluetun/internal/updater/providers/perfectprivacy"
"github.com/qdm12/gluetun/internal/updater/providers/pia" "github.com/qdm12/gluetun/internal/updater/providers/pia"
"github.com/qdm12/gluetun/internal/updater/providers/privado" "github.com/qdm12/gluetun/internal/updater/providers/privado"
"github.com/qdm12/gluetun/internal/updater/providers/privatevpn" "github.com/qdm12/gluetun/internal/updater/providers/privatevpn"
@@ -190,6 +191,27 @@ func (u *updater) updateNordvpn(ctx context.Context) (err error) {
return nil return nil
} }
func (u *updater) updatePerfectprivacy(ctx context.Context) (err error) {
minServers := getMinServers(len(u.servers.Perfectprivacy.Servers))
servers, warnings, err := perfectprivacy.GetServers(ctx, u.unzipper, minServers)
if u.options.CLI {
for _, warning := range warnings {
u.logger.Warn(constants.Perfectprivacy + ": " + warning)
}
}
if err != nil {
return err
}
if reflect.DeepEqual(u.servers.Perfectprivacy.Servers, servers) {
return nil
}
u.servers.Perfectprivacy.Timestamp = u.timeNow().Unix()
u.servers.Perfectprivacy.Servers = servers
return nil
}
func (u *updater) updatePIA(ctx context.Context) (err error) { func (u *updater) updatePIA(ctx context.Context) (err error) {
minServers := getMinServers(len(u.servers.Pia.Servers)) minServers := getMinServers(len(u.servers.Pia.Servers))
servers, err := pia.GetServers(ctx, u.client, minServers) servers, err := pia.GetServers(ctx, u.client, minServers)

View File

@@ -0,0 +1,45 @@
package perfectprivacy
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
type cityToServer map[string]models.PerfectprivacyServer
func (cts cityToServer) add(city string, ips []net.IP) {
server, ok := cts[city]
if !ok {
server.City = city
server.IPs = ips
server.TCP = true
server.UDP = true
} else {
// Do not insert duplicate IP addresses
existingIPs := make(map[string]struct{}, len(server.IPs))
for _, ip := range server.IPs {
existingIPs[ip.String()] = struct{}{}
}
for _, ip := range ips {
ipString := ip.String()
_, ok := existingIPs[ipString]
if ok {
continue
}
existingIPs[ipString] = struct{}{}
server.IPs = append(server.IPs, ip)
}
}
cts[city] = server
}
func (cts cityToServer) toServersSlice() (servers []models.PerfectprivacyServer) {
servers = make([]models.PerfectprivacyServer, 0, len(cts))
for _, server := range cts {
servers = append(servers, server)
}
return servers
}

View File

@@ -0,0 +1,20 @@
package perfectprivacy
import (
"strings"
"unicode"
)
func parseFilename(fileName string) (city string) {
const suffix = ".conf"
s := strings.TrimSuffix(fileName, suffix)
for i, r := range s {
if unicode.IsDigit(r) {
s = s[:i]
break
}
}
return s
}

View File

@@ -0,0 +1,73 @@
package perfectprivacy
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/updater/openvpn"
"github.com/qdm12/gluetun/internal/updater/unzip"
)
var ErrNotEnoughServers = errors.New("not enough servers found")
func GetServers(ctx context.Context, unzipper unzip.Unzipper, minServers int) (
servers []models.PerfectprivacyServer, warnings []string, err error) {
zipURL := url.URL{
Scheme: "https",
Host: "www.perfect-privacy.com",
Path: "/downloads/openvpn/get",
}
values := make(url.Values)
values.Set("system", "linux")
values.Set("scope", "server")
values.Set("filetype", "zip")
values.Set("protocol", "udp") // all support both TCP and UDP
zipURL.RawQuery = values.Encode()
contents, err := unzipper.FetchAndExtract(ctx, zipURL.String())
if err != nil {
return nil, nil, err
}
cts := make(cityToServer)
for fileName, content := range contents {
err := addServerFromOvpn(cts, fileName, content)
if err != nil {
warnings = append(warnings, fileName+": "+err.Error())
}
}
if len(cts) < minServers {
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
ErrNotEnoughServers, len(cts), minServers)
}
servers = cts.toServersSlice()
sortServers(servers)
return servers, warnings, nil
}
func addServerFromOvpn(cts cityToServer,
fileName string, content []byte) (err error) {
if !strings.HasSuffix(fileName, ".conf") {
return nil // not an OpenVPN file
}
ips, err := openvpn.ExtractIPs(content)
if err != nil {
return err
}
city := parseFilename(fileName)
cts.add(city, ips)
return nil
}

View File

@@ -0,0 +1,13 @@
package perfectprivacy
import (
"sort"
"github.com/qdm12/gluetun/internal/models"
)
func sortServers(servers []models.PerfectprivacyServer) {
sort.Slice(servers, func(i, j int) bool {
return servers[i].City < servers[j].City
})
}

View File

@@ -16,7 +16,8 @@ func zipExtractAll(zipBytes []byte) (contents map[string][]byte, err error) {
contents = map[string][]byte{} contents = map[string][]byte{}
for _, zf := range r.File { for _, zf := range r.File {
fileName := filepath.Base(zf.Name) fileName := filepath.Base(zf.Name)
if !strings.HasSuffix(fileName, ".ovpn") { if !strings.HasSuffix(fileName, ".ovpn") &&
!strings.HasSuffix(fileName, ".conf") {
continue continue
} }
f, err := zf.Open() f, err := zf.Open()

View File

@@ -14,8 +14,6 @@ var (
func (u *unzipper) FetchAndExtract(ctx context.Context, url string) ( func (u *unzipper) FetchAndExtract(ctx context.Context, url string) (
contents map[string][]byte, err error) { contents map[string][]byte, err error) {
contents = make(map[string][]byte)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -40,13 +38,5 @@ func (u *unzipper) FetchAndExtract(ctx context.Context, url string) (
return nil, err return nil, err
} }
newContents, err := zipExtractAll(b) return zipExtractAll(b)
if err != nil {
return nil, err
}
for fileName, content := range newContents {
contents[fileName] = content
}
return contents, nil
} }

View File

@@ -132,6 +132,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
} }
} }
if u.options.Perfectprivacy {
u.logger.Info("updating " + constants.Perfectprivacy + " servers...")
if err := u.updatePerfectprivacy(ctx); err != nil {
if ctxErr := ctx.Err(); ctxErr != nil {
return allServers, ctxErr
}
u.logger.Error(err.Error())
}
}
if u.options.Privado { if u.options.Privado {
u.logger.Info("updating Privado servers...") u.logger.Info("updating Privado servers...")
if err := u.updatePrivado(ctx); err != nil { if err := u.updatePrivado(ctx); err != nil {