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

@@ -9,27 +9,28 @@ import (
) )
type Updater struct { type Updater struct {
Period time.Duration `json:"period"` Period time.Duration `json:"period"`
DNSAddress string `json:"dns_address"` DNSAddress string `json:"dns_address"`
Cyberghost bool `json:"cyberghost"` Cyberghost bool `json:"cyberghost"`
Expressvpn bool `json:"expressvpn"` Expressvpn bool `json:"expressvpn"`
Fastestvpn bool `json:"fastestvpn"` Fastestvpn bool `json:"fastestvpn"`
HideMyAss bool `json:"hidemyass"` HideMyAss bool `json:"hidemyass"`
Ipvanish bool `json:"ipvanish"` Ipvanish bool `json:"ipvanish"`
Ivpn bool `json:"ivpn"` Ivpn bool `json:"ivpn"`
Mullvad bool `json:"mullvad"` Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"` Nordvpn bool `json:"nordvpn"`
PIA bool `json:"pia"` Perfectprivacy bool `json:"perfectprivacy"`
Privado bool `json:"privado"` PIA bool `json:"pia"`
Privatevpn bool `json:"privatevpn"` Privado bool `json:"privado"`
Protonvpn bool `json:"protonvpn"` Privatevpn bool `json:"privatevpn"`
Purevpn bool `json:"purevpn"` Protonvpn bool `json:"protonvpn"`
Surfshark bool `json:"surfshark"` Purevpn bool `json:"purevpn"`
Torguard bool `json:"torguard"` Surfshark bool `json:"surfshark"`
VPNUnlimited bool `json:"vpnunlimited"` Torguard bool `json:"torguard"`
Vyprvpn bool `json:"vyprvpn"` VPNUnlimited bool `json:"vpnunlimited"`
Wevpn bool `json:"wevpn"` Vyprvpn bool `json:"vyprvpn"`
Windscribe bool `json:"windscribe"` Wevpn bool `json:"wevpn"`
Windscribe bool `json:"windscribe"`
// The two below should be used in CLI mode only // The two below should be used in CLI mode only
CLI bool `json:"-"` CLI bool `json:"-"`
} }
@@ -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

@@ -1,26 +1,27 @@
package models package models
type AllServers struct { type AllServers struct {
Version uint16 `json:"version"` // used for migration of the top level scheme Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost CyberghostServers `json:"cyberghost"` Cyberghost CyberghostServers `json:"cyberghost"`
Expressvpn ExpressvpnServers `json:"expressvpn"` Expressvpn ExpressvpnServers `json:"expressvpn"`
Fastestvpn FastestvpnServers `json:"fastestvpn"` Fastestvpn FastestvpnServers `json:"fastestvpn"`
HideMyAss HideMyAssServers `json:"hidemyass"` HideMyAss HideMyAssServers `json:"hidemyass"`
Ipvanish IpvanishServers `json:"ipvanish"` Ipvanish IpvanishServers `json:"ipvanish"`
Ivpn IvpnServers `json:"ivpn"` Ivpn IvpnServers `json:"ivpn"`
Mullvad MullvadServers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Nordvpn NordvpnServers `json:"nordvpn"` Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"`
Privado PrivadoServers `json:"privado"` Nordvpn NordvpnServers `json:"nordvpn"`
Pia PiaServers `json:"pia"` Privado PrivadoServers `json:"privado"`
Privatevpn PrivatevpnServers `json:"privatevpn"` Pia PiaServers `json:"pia"`
Protonvpn ProtonvpnServers `json:"protonvpn"` Privatevpn PrivatevpnServers `json:"privatevpn"`
Purevpn PurevpnServers `json:"purevpn"` Protonvpn ProtonvpnServers `json:"protonvpn"`
Surfshark SurfsharkServers `json:"surfshark"` Purevpn PurevpnServers `json:"purevpn"`
Torguard TorguardServers `json:"torguard"` Surfshark SurfsharkServers `json:"surfshark"`
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"` Torguard TorguardServers `json:"torguard"`
Vyprvpn VyprvpnServers `json:"vyprvpn"` VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
Wevpn WevpnServers `json:"wevpn"` Vyprvpn VyprvpnServers `json:"vyprvpn"`
Windscribe WindscribeServers `json:"windscribe"` Wevpn WevpnServers `json:"wevpn"`
Windscribe WindscribeServers `json:"windscribe"`
} }
func (a *AllServers) Count() int { func (a *AllServers) Count() int {
@@ -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

@@ -28,26 +28,27 @@ func (s *Storage) logTimeDiff(provider string, persistedUnix, hardcodedUnix int6
func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.AllServers { func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.AllServers {
return models.AllServers{ return models.AllServers{
Version: hardcoded.Version, Version: hardcoded.Version,
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost), Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
Expressvpn: s.mergeExpressvpn(hardcoded.Expressvpn, persisted.Expressvpn), Expressvpn: s.mergeExpressvpn(hardcoded.Expressvpn, persisted.Expressvpn),
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn), Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss), HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish), Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish),
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn), Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), Perfectprivacy: s.mergePerfectprivacy(hardcoded.Perfectprivacy, persisted.Perfectprivacy),
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia), Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn), Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn), Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn), Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn),
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark), Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard), Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited), Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn), VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited),
Wevpn: s.mergeWevpn(hardcoded.Wevpn, persisted.Wevpn), Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn),
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe), Wevpn: s.mergeWevpn(hardcoded.Wevpn, persisted.Wevpn),
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe),
} }
} }
@@ -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 {
@@ -231,26 +240,27 @@ func (s *Storage) extractServersFromBytes(b []byte, hardcoded models.AllServers)
// allVersions is a subset of models.AllServers structure used to track // allVersions is a subset of models.AllServers structure used to track
// versions to avoid unmarshaling errors. // versions to avoid unmarshaling errors.
type allVersions struct { type allVersions struct {
Version uint16 `json:"version"` // used for migration of the top level scheme Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost serverVersion `json:"cyberghost"` Cyberghost serverVersion `json:"cyberghost"`
Expressvpn serverVersion `json:"expressvpn"` Expressvpn serverVersion `json:"expressvpn"`
Fastestvpn serverVersion `json:"fastestvpn"` Fastestvpn serverVersion `json:"fastestvpn"`
HideMyAss serverVersion `json:"hidemyass"` HideMyAss serverVersion `json:"hidemyass"`
Ipvanish serverVersion `json:"ipvanish"` Ipvanish serverVersion `json:"ipvanish"`
Ivpn serverVersion `json:"ivpn"` Ivpn serverVersion `json:"ivpn"`
Mullvad serverVersion `json:"mullvad"` Mullvad serverVersion `json:"mullvad"`
Nordvpn serverVersion `json:"nordvpn"` Nordvpn serverVersion `json:"nordvpn"`
Privado serverVersion `json:"privado"` Perfectprivacy serverVersion `json:"perfectprivacy"`
Pia serverVersion `json:"pia"` Privado serverVersion `json:"privado"`
Privatevpn serverVersion `json:"privatevpn"` Pia serverVersion `json:"pia"`
Protonvpn serverVersion `json:"protonvpn"` Privatevpn serverVersion `json:"privatevpn"`
Purevpn serverVersion `json:"purevpn"` Protonvpn serverVersion `json:"protonvpn"`
Surfshark serverVersion `json:"surfshark"` Purevpn serverVersion `json:"purevpn"`
Torguard serverVersion `json:"torguard"` Surfshark serverVersion `json:"surfshark"`
VPNUnlimited serverVersion `json:"vpnunlimited"` Torguard serverVersion `json:"torguard"`
Vyprvpn serverVersion `json:"vyprvpn"` VPNUnlimited serverVersion `json:"vpnunlimited"`
Wevpn serverVersion `json:"wevpn"` Vyprvpn serverVersion `json:"vyprvpn"`
Windscribe serverVersion `json:"windscribe"` Wevpn serverVersion `json:"wevpn"`
Windscribe serverVersion `json:"windscribe"`
} }
type serverVersion struct { type serverVersion struct {
@@ -259,24 +269,25 @@ type serverVersion struct {
// allJSONRawMessages is to delay decoding of each provider servers. // allJSONRawMessages is to delay decoding of each provider servers.
type allJSONRawMessages struct { type allJSONRawMessages struct {
Version uint16 `json:"version"` // used for migration of the top level scheme Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost json.RawMessage `json:"cyberghost"` Cyberghost json.RawMessage `json:"cyberghost"`
Expressvpn json.RawMessage `json:"expressvpn"` Expressvpn json.RawMessage `json:"expressvpn"`
Fastestvpn json.RawMessage `json:"fastestvpn"` Fastestvpn json.RawMessage `json:"fastestvpn"`
HideMyAss json.RawMessage `json:"hidemyass"` HideMyAss json.RawMessage `json:"hidemyass"`
Ipvanish json.RawMessage `json:"ipvanish"` Ipvanish json.RawMessage `json:"ipvanish"`
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"`
Privado json.RawMessage `json:"privado"` Perfectprivacy json.RawMessage `json:"perfectprivacy"`
Pia json.RawMessage `json:"pia"` Privado json.RawMessage `json:"privado"`
Privatevpn json.RawMessage `json:"privatevpn"` Pia json.RawMessage `json:"pia"`
Protonvpn json.RawMessage `json:"protonvpn"` Privatevpn json.RawMessage `json:"privatevpn"`
Purevpn json.RawMessage `json:"purevpn"` Protonvpn json.RawMessage `json:"protonvpn"`
Surfshark json.RawMessage `json:"surfshark"` Purevpn json.RawMessage `json:"purevpn"`
Torguard json.RawMessage `json:"torguard"` Surfshark json.RawMessage `json:"surfshark"`
VPNUnlimited json.RawMessage `json:"vpnunlimited"` Torguard json.RawMessage `json:"torguard"`
Vyprvpn json.RawMessage `json:"vyprvpn"` VPNUnlimited json.RawMessage `json:"vpnunlimited"`
Wevpn json.RawMessage `json:"wevpn"` Vyprvpn json.RawMessage `json:"vyprvpn"`
Windscribe json.RawMessage `json:"windscribe"` Wevpn json.RawMessage `json:"wevpn"`
Windscribe json.RawMessage `json:"windscribe"`
} }

View File

@@ -30,25 +30,26 @@ func Test_extractServersFromBytes(t *testing.T) {
"different versions": { "different versions": {
b: []byte(`{}`), b: []byte(`{}`),
hardcoded: models.AllServers{ hardcoded: models.AllServers{
Cyberghost: models.CyberghostServers{Version: 1}, Cyberghost: models.CyberghostServers{Version: 1},
Expressvpn: models.ExpressvpnServers{Version: 1}, Expressvpn: models.ExpressvpnServers{Version: 1},
Fastestvpn: models.FastestvpnServers{Version: 1}, Fastestvpn: models.FastestvpnServers{Version: 1},
HideMyAss: models.HideMyAssServers{Version: 1}, HideMyAss: models.HideMyAssServers{Version: 1},
Ipvanish: models.IpvanishServers{Version: 1}, Ipvanish: models.IpvanishServers{Version: 1},
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},
Privado: models.PrivadoServers{Version: 1}, Perfectprivacy: models.PerfectprivacyServers{Version: 1},
Pia: models.PiaServers{Version: 1}, Privado: models.PrivadoServers{Version: 1},
Privatevpn: models.PrivatevpnServers{Version: 1}, Pia: models.PiaServers{Version: 1},
Protonvpn: models.ProtonvpnServers{Version: 1}, Privatevpn: models.PrivatevpnServers{Version: 1},
Purevpn: models.PurevpnServers{Version: 1}, Protonvpn: models.ProtonvpnServers{Version: 1},
Surfshark: models.SurfsharkServers{Version: 1}, Purevpn: models.PurevpnServers{Version: 1},
Torguard: models.TorguardServers{Version: 1}, Surfshark: models.SurfsharkServers{Version: 1},
VPNUnlimited: models.VPNUnlimitedServers{Version: 1}, Torguard: models.TorguardServers{Version: 1},
Vyprvpn: models.VyprvpnServers{Version: 1}, VPNUnlimited: models.VPNUnlimitedServers{Version: 1},
Wevpn: models.WevpnServers{Version: 1}, Vyprvpn: models.VyprvpnServers{Version: 1},
Windscribe: models.WindscribeServers{Version: 1}, Wevpn: models.WevpnServers{Version: 1},
Windscribe: models.WindscribeServers{Version: 1},
}, },
logged: []string{ logged: []string{
"Cyberghost servers from file discarded because they have version 0 and hardcoded servers have version 1", "Cyberghost servers from file discarded because they have version 0 and hardcoded servers have 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},
@@ -95,46 +98,48 @@ func Test_extractServersFromBytes(t *testing.T) {
"windscribe": {"version": 1, "timestamp": 1} "windscribe": {"version": 1, "timestamp": 1}
}`), }`),
hardcoded: models.AllServers{ hardcoded: models.AllServers{
Cyberghost: models.CyberghostServers{Version: 1}, Cyberghost: models.CyberghostServers{Version: 1},
Expressvpn: models.ExpressvpnServers{Version: 1}, Expressvpn: models.ExpressvpnServers{Version: 1},
Fastestvpn: models.FastestvpnServers{Version: 1}, Fastestvpn: models.FastestvpnServers{Version: 1},
HideMyAss: models.HideMyAssServers{Version: 1}, HideMyAss: models.HideMyAssServers{Version: 1},
Ipvanish: models.IpvanishServers{Version: 1}, Ipvanish: models.IpvanishServers{Version: 1},
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},
Privado: models.PrivadoServers{Version: 1}, Perfectprivacy: models.PerfectprivacyServers{Version: 1},
Pia: models.PiaServers{Version: 1}, Privado: models.PrivadoServers{Version: 1},
Privatevpn: models.PrivatevpnServers{Version: 1}, Pia: models.PiaServers{Version: 1},
Protonvpn: models.ProtonvpnServers{Version: 1}, Privatevpn: models.PrivatevpnServers{Version: 1},
Purevpn: models.PurevpnServers{Version: 1}, Protonvpn: models.ProtonvpnServers{Version: 1},
Surfshark: models.SurfsharkServers{Version: 1}, Purevpn: models.PurevpnServers{Version: 1},
Torguard: models.TorguardServers{Version: 1}, Surfshark: models.SurfsharkServers{Version: 1},
VPNUnlimited: models.VPNUnlimitedServers{Version: 1}, Torguard: models.TorguardServers{Version: 1},
Vyprvpn: models.VyprvpnServers{Version: 1}, VPNUnlimited: models.VPNUnlimitedServers{Version: 1},
Wevpn: models.WevpnServers{Version: 1}, Vyprvpn: models.VyprvpnServers{Version: 1},
Windscribe: models.WindscribeServers{Version: 1}, Wevpn: models.WevpnServers{Version: 1},
Windscribe: models.WindscribeServers{Version: 1},
}, },
persisted: models.AllServers{ persisted: models.AllServers{
Cyberghost: models.CyberghostServers{Version: 1, Timestamp: 1}, Cyberghost: models.CyberghostServers{Version: 1, Timestamp: 1},
Expressvpn: models.ExpressvpnServers{Version: 1, Timestamp: 1}, Expressvpn: models.ExpressvpnServers{Version: 1, Timestamp: 1},
Fastestvpn: models.FastestvpnServers{Version: 1, Timestamp: 1}, Fastestvpn: models.FastestvpnServers{Version: 1, Timestamp: 1},
HideMyAss: models.HideMyAssServers{Version: 1, Timestamp: 1}, HideMyAss: models.HideMyAssServers{Version: 1, Timestamp: 1},
Ipvanish: models.IpvanishServers{Version: 1, Timestamp: 1}, Ipvanish: models.IpvanishServers{Version: 1, Timestamp: 1},
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},
Privado: models.PrivadoServers{Version: 1, Timestamp: 1}, Perfectprivacy: models.PerfectprivacyServers{Version: 1, Timestamp: 1},
Pia: models.PiaServers{Version: 1, Timestamp: 1}, Privado: models.PrivadoServers{Version: 1, Timestamp: 1},
Privatevpn: models.PrivatevpnServers{Version: 1, Timestamp: 1}, Pia: models.PiaServers{Version: 1, Timestamp: 1},
Protonvpn: models.ProtonvpnServers{Version: 1, Timestamp: 1}, Privatevpn: models.PrivatevpnServers{Version: 1, Timestamp: 1},
Purevpn: models.PurevpnServers{Version: 1, Timestamp: 1}, Protonvpn: models.ProtonvpnServers{Version: 1, Timestamp: 1},
Surfshark: models.SurfsharkServers{Version: 1, Timestamp: 1}, Purevpn: models.PurevpnServers{Version: 1, Timestamp: 1},
Torguard: models.TorguardServers{Version: 1, Timestamp: 1}, Surfshark: models.SurfsharkServers{Version: 1, Timestamp: 1},
VPNUnlimited: models.VPNUnlimitedServers{Version: 1, Timestamp: 1}, Torguard: models.TorguardServers{Version: 1, Timestamp: 1},
Vyprvpn: models.VyprvpnServers{Version: 1, Timestamp: 1}, VPNUnlimited: models.VPNUnlimitedServers{Version: 1, Timestamp: 1},
Wevpn: models.WevpnServers{Version: 1, Timestamp: 1}, Vyprvpn: models.VyprvpnServers{Version: 1, Timestamp: 1},
Windscribe: models.WindscribeServers{Version: 1, Timestamp: 1}, Wevpn: models.WevpnServers{Version: 1, Timestamp: 1},
Windscribe: models.WindscribeServers{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,
@@ -111526,4 +111941,4 @@
} }
] ]
} }
} }

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 {