diff --git a/.env.purevpn.example b/.env.purevpn.example
new file mode 100644
index 000000000..2c3b6dc6d
--- /dev/null
+++ b/.env.purevpn.example
@@ -0,0 +1,4 @@
+PUREVPN_USER=your-username
+PUREVPN_PASSWORD=your-password
+# Optional timezone for container logs
+TZ=UTC
diff --git a/.gitignore b/.gitignore
index c630cce1d..15ff7373b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
scratch.txt
+.env.purevpn
.DS_Store
diff --git a/internal/configuration/settings/openvpnselection_test.go b/internal/configuration/settings/openvpnselection_test.go
new file mode 100644
index 000000000..4c1f1098a
--- /dev/null
+++ b/internal/configuration/settings/openvpnselection_test.go
@@ -0,0 +1,62 @@
+package settings
+
+import (
+ "testing"
+
+ "github.com/qdm12/gluetun/internal/constants"
+ "github.com/qdm12/gluetun/internal/constants/providers"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_OpenVPNSelection_validate(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ selection OpenVPNSelection
+ provider string
+ err error
+ }{
+ "purevpn default selection is valid": {
+ selection: openVPNSelectionForValidation(providers.Purevpn),
+ provider: providers.Purevpn,
+ },
+ "purevpn TCP without custom port is valid": {
+ selection: func() OpenVPNSelection {
+ s := openVPNSelectionForValidation(providers.Purevpn)
+ s.Protocol = constants.TCP
+ return s
+ }(),
+ provider: providers.Purevpn,
+ },
+ "purevpn custom port is rejected": {
+ selection: func() OpenVPNSelection {
+ s := openVPNSelectionForValidation(providers.Purevpn)
+ *s.CustomPort = 1194
+ return s
+ }(),
+ provider: providers.Purevpn,
+ err: ErrOpenVPNCustomPortNotAllowed,
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ err := testCase.selection.validate(testCase.provider)
+ if testCase.err == nil {
+ require.NoError(t, err)
+ return
+ }
+ require.Error(t, err)
+ assert.ErrorIs(t, err, testCase.err)
+ })
+ }
+}
+
+func openVPNSelectionForValidation(provider string) OpenVPNSelection {
+ selection := OpenVPNSelection{}
+ selection.setDefaults(provider)
+ return selection
+}
diff --git a/internal/configuration/settings/serverselection.go b/internal/configuration/settings/serverselection.go
index 3781e8189..47598afee 100644
--- a/internal/configuration/settings/serverselection.go
+++ b/internal/configuration/settings/serverselection.go
@@ -103,6 +103,10 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
*ss = nordvpnRetroRegion(*ss, filterChoices.Regions, filterChoices.Countries)
case providers.Surfshark:
*ss = surfsharkRetroRegion(*ss)
+ case providers.Purevpn:
+ // Keep parsing SERVER_REGIONS for retro-compatibility, but
+ // do not apply it to PureVPN filtering.
+ ss.Regions = nil
}
err = validateServerFilters(*ss, filterChoices, vpnServiceProvider, warner)
diff --git a/internal/models/server.go b/internal/models/server.go
index 1ae92fb6a..fedd42962 100644
--- a/internal/models/server.go
+++ b/internal/models/server.go
@@ -13,29 +13,33 @@ import (
type Server struct {
VPN string `json:"vpn,omitempty"`
// Surfshark: country is also used for multi-hop
- Country string `json:"country,omitempty"`
- Region string `json:"region,omitempty"`
- City string `json:"city,omitempty"`
- ISP string `json:"isp,omitempty"`
- Categories []string `json:"categories,omitempty"`
- Owned bool `json:"owned,omitempty"`
- Number uint16 `json:"number,omitempty"`
- ServerName string `json:"server_name,omitempty"`
- Hostname string `json:"hostname,omitempty"`
- TCP bool `json:"tcp,omitempty"`
- UDP bool `json:"udp,omitempty"`
- OvpnX509 string `json:"x509,omitempty"`
- RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
- MultiHop bool `json:"multihop,omitempty"`
- WgPubKey string `json:"wgpubkey,omitempty"`
- Free bool `json:"free,omitempty"` // TODO v4 create a SubscriptionTier struct
- Premium bool `json:"premium,omitempty"`
- Stream bool `json:"stream,omitempty"` // TODO v4 create a Features struct
- SecureCore bool `json:"secure_core,omitempty"`
- Tor bool `json:"tor,omitempty"`
- PortForward bool `json:"port_forward,omitempty"`
- Keep bool `json:"keep,omitempty"`
- IPs []netip.Addr `json:"ips,omitempty"`
+ Country string `json:"country,omitempty"`
+ Region string `json:"region,omitempty"`
+ City string `json:"city,omitempty"`
+ ISP string `json:"isp,omitempty"`
+ Categories []string `json:"categories,omitempty"`
+ Owned bool `json:"owned,omitempty"`
+ Number uint16 `json:"number,omitempty"`
+ ServerName string `json:"server_name,omitempty"`
+ Hostname string `json:"hostname,omitempty"`
+ TCP bool `json:"tcp,omitempty"`
+ UDP bool `json:"udp,omitempty"`
+ TCPPorts []uint16 `json:"tcp_ports,omitempty"`
+ UDPPorts []uint16 `json:"udp_ports,omitempty"`
+ OvpnX509 string `json:"x509,omitempty"`
+ RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
+ MultiHop bool `json:"multihop,omitempty"`
+ WgPubKey string `json:"wgpubkey,omitempty"`
+ Free bool `json:"free,omitempty"` // TODO v4 create a SubscriptionTier struct
+ Premium bool `json:"premium,omitempty"`
+ Stream bool `json:"stream,omitempty"` // TODO v4 create a Features struct
+ SecureCore bool `json:"secure_core,omitempty"`
+ Tor bool `json:"tor,omitempty"`
+ PortForward bool `json:"port_forward,omitempty"`
+ QuantumResistant bool `json:"quantum_resistant,omitempty"`
+ Obfuscated bool `json:"obfuscated,omitempty"`
+ Keep bool `json:"keep,omitempty"`
+ IPs []netip.Addr `json:"ips,omitempty"`
}
var (
diff --git a/internal/models/server_test.go b/internal/models/server_test.go
index 8cdd7de16..fc61b99dc 100644
--- a/internal/models/server_test.go
+++ b/internal/models/server_test.go
@@ -43,52 +43,56 @@ func Test_Server_Equal(t *testing.T) {
},
"all fields equal": {
a: &Server{
- VPN: "vpn",
- Country: "country",
- Region: "region",
- City: "city",
- ISP: "isp",
- Owned: true,
- Number: 1,
- ServerName: "server_name",
- Hostname: "hostname",
- TCP: true,
- UDP: true,
- OvpnX509: "x509",
- RetroLoc: "retroloc",
- MultiHop: true,
- WgPubKey: "wgpubkey",
- Free: true,
- Stream: true,
- SecureCore: true,
- Tor: true,
- PortForward: true,
- IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
- Keep: true,
+ VPN: "vpn",
+ Country: "country",
+ Region: "region",
+ City: "city",
+ ISP: "isp",
+ Owned: true,
+ Number: 1,
+ ServerName: "server_name",
+ Hostname: "hostname",
+ TCP: true,
+ UDP: true,
+ OvpnX509: "x509",
+ RetroLoc: "retroloc",
+ MultiHop: true,
+ WgPubKey: "wgpubkey",
+ Free: true,
+ Stream: true,
+ SecureCore: true,
+ Tor: true,
+ PortForward: true,
+ QuantumResistant: true,
+ Obfuscated: true,
+ IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
+ Keep: true,
},
b: Server{
- VPN: "vpn",
- Country: "country",
- Region: "region",
- City: "city",
- ISP: "isp",
- Owned: true,
- Number: 1,
- ServerName: "server_name",
- Hostname: "hostname",
- TCP: true,
- UDP: true,
- OvpnX509: "x509",
- RetroLoc: "retroloc",
- MultiHop: true,
- WgPubKey: "wgpubkey",
- Free: true,
- Stream: true,
- SecureCore: true,
- Tor: true,
- PortForward: true,
- IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
- Keep: true,
+ VPN: "vpn",
+ Country: "country",
+ Region: "region",
+ City: "city",
+ ISP: "isp",
+ Owned: true,
+ Number: 1,
+ ServerName: "server_name",
+ Hostname: "hostname",
+ TCP: true,
+ UDP: true,
+ OvpnX509: "x509",
+ RetroLoc: "retroloc",
+ MultiHop: true,
+ WgPubKey: "wgpubkey",
+ Free: true,
+ Stream: true,
+ SecureCore: true,
+ Tor: true,
+ PortForward: true,
+ QuantumResistant: true,
+ Obfuscated: true,
+ IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
+ Keep: true,
},
equal: true,
},
diff --git a/internal/provider/providers.go b/internal/provider/providers.go
index aea562be2..9173b5404 100644
--- a/internal/provider/providers.go
+++ b/internal/provider/providers.go
@@ -75,7 +75,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
providers.PrivateInternetAccess: privateinternetaccess.New(storage, randSource, timeNow, client),
providers.Privatevpn: privatevpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
providers.Protonvpn: protonvpn.New(storage, randSource, client, updaterWarner, *credentials.ProtonEmail, *credentials.ProtonPassword),
- providers.Purevpn: purevpn.New(storage, randSource, ipFetcher, unzipper, updaterWarner, parallelResolver),
+ providers.Purevpn: purevpn.New(storage, randSource, client, ipFetcher, unzipper, updaterWarner, parallelResolver),
providers.SlickVPN: slickvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
providers.Surfshark: surfshark.New(storage, randSource, client, unzipper, updaterWarner, parallelResolver),
providers.Torguard: torguard.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
diff --git a/internal/provider/purevpn/connection.go b/internal/provider/purevpn/connection.go
index 6f9bf61a3..df7d1a343 100644
--- a/internal/provider/purevpn/connection.go
+++ b/internal/provider/purevpn/connection.go
@@ -9,7 +9,7 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
connection models.Connection, err error,
) {
- defaults := utils.NewConnectionDefaults(80, 53, 0) //nolint:mnd
+ defaults := utils.NewConnectionDefaults(80, 15021, 0)
return utils.GetConnection(p.Name(),
p.storage, selection, defaults, ipv6Supported, p.randSource)
}
diff --git a/internal/provider/purevpn/openvpnconf.go b/internal/provider/purevpn/openvpnconf.go
index bc76c12aa..61afbefc9 100644
--- a/internal/provider/purevpn/openvpnconf.go
+++ b/internal/provider/purevpn/openvpnconf.go
@@ -17,6 +17,15 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
openvpn.AES256gcm,
},
KeyDirection: "1",
+ UDPLines: []string{
+ "explicit-exit-notify 2",
+ },
+ ExtraLines: []string{
+ "compress",
+ "route-method exe",
+ "route-delay 0",
+ "script-security 2",
+ },
CAs: []string{
"MIIF8jCCA9qgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBkjELMAkGA1UEBhMCVkcxEDAOBgNVBAgTB1RvcnRvbGExETAPBgNVBAcTCFJvYWR0b3duMRcwFQYDVQQKEw5TZWN1cmUtU2VydmVyUTELMAkGA1UECxMCSVQxFzAVBgNVBAMTDlNlY3VyZS1TZXJ2ZXJRMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWluMB4XDTIyMDQyMDA2NTkyMFoXDTI5MDcyMjA2NTkyMFowfjELMAkGA1UEBhMCVkcxEDAOBgNVBAgTB1RvcnRvbGExFzAVBgNVBAoTDlNlY3VyZS1TZXJ2ZXJRMQswCQYDVQQLEwJJVDEWMBQGA1UEAxMNU2VjdXJlLUludGVyUTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALONGBemKjG4mn9BrzByTCjOmPKy9hGxMBq0dFQsFVpd5o9PG95QK+rjpApi5zKzrkVu9t2L0I1NsXNhU5KM0SQAk58U9qaA771g6Y4HuGs73K5ginNIH9910idpX/VBxx2SyHc5G8OddUFs0y+pbJz1QVgq+HZDEpmQ2EI/HAit4cbaesaoY25/B0Os7KYjyUhT3dkYDV9RaNkcN74Q2/B5oJvIMqQrOLZM/v2JC7PYZxvzfY0tI1ud4UF2po27ih215uKSkl/POtTjVRoCl7Ki9gQQEg7WPTTYSQ/2w0v34UwHbDCgUCGhcY5SWOy91FBhGhCDe4yI0IjLPF3ik+auygOUks6iaF4xQmsiJs6SKngRn1lLEtyNLNhyH1whAl4Y/w24ZVcgaD0BQ7oytfBdZRrm0l3G65CUMZG/szpZg2aKqQ2pWMfaA8ddvOa/ZZqnJZoOYBytXzatJRewAqpKetWdHHMQcQaJYWslR7HYrFs8ZU0z8wcOdka1mCYy8zlTi8omSyatB4pOnUtbM8Q8t2fwqGq0QrscfWt86dh/JRCZqvarzYHxmmve6ZMnpZVII1l6/owDUS57VWulDyMxIz38BBhB9zNAyu4ZS+FFb1YtdEps+J3D6xgr03C2AdHgYu3PYuJAj0zJEWb5rCAet5N9pBAUToz3NPAHPxF/AgMBAAGjZjBkMB0GA1UdDgQWBBSQHevnqcnlAw/o2QEVK4rpOBypEjAfBgNVHSMEGDAWgBSwL9/K/adBEASDpofY5CHz0dHm4jASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAxKa97spo7hBUMFzN/DUy10rUFSrv8fKAGAvg/JxvM0QNU/S2MO7y4pniNg3HE6yLuus6NoSkjhDbsBNCBcogISzxYKSEzwJWoQk8P/vqSFD4GCIuPntnpKfGEeYh1yW5xJQNzgBPB2qrhuwv2O/rZVB1PGVO5XS4ttDlQeAjxn8Q61U5hJ1MAH8uJ0Bc2RaymFgVeDXIrOkYSomE1HBJMEAjkQ7jlgPv/+QEDG+XNnlEl2Rz4mXJ6XfnB4PgxGNBN3PC+DuoSuW/P677VVQpm3CpEO6srGxbK407mbfKm4k8WCFKDMRfHScsgLF95gFaxt14iE9Wda68HlChtGxnF0M7Pb1EH2niodYRoKHQUcMjI5Mzy2Ug7vuY1PfRqUPhlse/LaX1pWRw0Pfe80V4oKTX6UfeyTftPeFtlM9N078wXWI5W6XOx81Rc/54tO0JsQ7mb+N+jgRlM60QcFbrcjtEVnCJPx1kowXgZWJwzfYx/loYtATETy+4s3NRm9csjaG/BiUNfoz7I38a+ZYzSfD7tNRgm6v1qpIMcDnH89xoH2H3RuRdm0VSlm4M7Hhb/YuMbB4h0PL/kJ+4KnnFUEWIO3prziwccuP34EUdmTVot0CGlvoVmPSzdOzMsCBIBYQ6/qF5LWcb4aSJcOtePacG5PmeyET8RP+4zO6theI=", //nolint:lll
"MIIGBzCCA++gAwIBAgIULjehn3oKy7VgPWVqBLqG3RcBw6AwDQYJKoZIhvcNAQELBQAwgZIxCzAJBgNVBAYTAlZHMRAwDgYDVQQIEwdUb3J0b2xhMREwDwYDVQQHEwhSb2FkdG93bjEXMBUGA1UEChMOU2VjdXJlLVNlcnZlclExCzAJBgNVBAsTAklUMRcwFQYDVQQDEw5TZWN1cmUtU2VydmVyUTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjAeFw0yMjA0MjAwNjUxNTFaFw0zMjA0MTcwNjUxNTFaMIGSMQswCQYDVQQGEwJWRzEQMA4GA1UECBMHVG9ydG9sYTERMA8GA1UEBxMIUm9hZHRvd24xFzAVBgNVBAoTDlNlY3VyZS1TZXJ2ZXJRMQswCQYDVQQLEwJJVDEXMBUGA1UEAxMOU2VjdXJlLVNlcnZlclExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDYBqR63rzysa2c/1YTn811McVXAvkqV1smE3jLv1TP4VW/nD67Sb43iKc/lhkbgXV89PFQt6BswK8BPC5TzXi/kTFJtxkN79L9insG+DFiz/NvKRWxdAbKJZtv7c2eBLYOAflYcI/HwkBJa01uvPtGtCKOqfhwB120Kwq1gxr95DTU4OtPm8PRfUookiCCFb7qip6twABfcC5lntI3UBN1CQfiCtgdY32+7doeFURH+jY9JS4Ots78LKVN8GiMUxJosSHGxw2+/ERwD6IiJO5AeRIgBSSa2GW3WNlQ4qHTq0obVDoK3+xMAbhbRjVYriynYPB70mN82lWN1chXaiDeW/l0g7DU/EJKCAkYLlMr2hI1kMTu9AYHKUH/NsEC1Z8Nf6GCxi9zlOcuANNNxxioDeUEANoMCRRb1hQDx83udxSLTbR8qCO2+G2EJp/L9M/efGn6L7U7qvKxzua8ZbLAWKMwFtqVRD0+oZPN6rEVFrOx9byz6DFA6vKa76dpdLbISnOrqyQVxkZMhBuL/fFbHyLWxD9QN9dnVx8q3W8fhJXdDln4oMOzyMm/0K0iar7GLjGKQ3Zmz9qJ1lWCdyA800UbJ5eeD4SXmB2eYZnQxW8MGmHygz0mslBzhN7mB+7sxMIiLFiCc6SqYu6ONDOVEe0T+H0pka1yN6o/9TLJtwIDAQABo1MwUTAdBgNVHQ4EFgQUsC/fyv2nQRAEg6aH2OQh89HR5uIwHwYDVR0jBBgwFoAUsC/fyv2nQRAEg6aH2OQh89HR5uIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAnklSAVjZLlyy0iaM4g29+t87RDUfMAEkJEq+qq23Ovrvw9XPr8xfp3rhPgY/12EQofwWuToIQeRawZJ9ZKq3+ELpOZAEGkuA22vQdYaulY8suUXWuD4hFCvsKWA/jASrEY29l54r0yCcElrN5upqm7BoRbHYFieO0ieBmGaLoxAqjZc99KkO4QELXtn7OMsXmXTUwlA8m9acTDKmpl6cVs2Cq/Foz6NbbWvCb65q1HZSmfkXB8mCZnLF+1wERpQeTpnA0cNT4RUGTe2PQsTXOBgASEabO7AFDkg2H7YgmfBwVZKwHZWo72ggSdHUygKOT1+v9Xt1oFg3k6l/GiyVsvCSzN0G/7VzDJuAIRtDIs/daDhXxyHaAqbKQ8VDHuLBxMTYQQnndt2D6J7XxtQ2F/iWqDZw+l8gukFwrgOMgq7ZYYeOOxKx20zbBAUELYtNF2KaLJjKiZJmQd/1OjuKYexggFWBC2f1OiDzxzrqAocSnGllVPmmh0ALJCi8eMT5lt9sfZq5hWPYnwDYeVQ1A/5l7x+VbcqeQAJCYh/RIy60Tp7QYeliECJDkowDGtIcz+v97FkcTsL+8r+xbM3z3f3oQSYTJEBPe8DnGAyveCuwo0trH4kGLiAiqS+2mR0pMhDFIXXgL9EF/S7KkHT9Wfn6FE0jGgjbe2PZOrN9Ts0=", //nolint:lll
@@ -25,5 +34,6 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
RSAKey: "MIIJKQIBAAKCAgEAs3ipM4VrLJm7TZ5U2lAS3E202rc97CjJUThXBm3tGwnO0g7L1duFEr1FcD2/WDufeTVGf05lczmheubO8WBXFMjMB70llJRUK5nPpv2BSfxcwImwehX0NjsfqhVyaZzGPEz5lP5gtQBPpQdZq8CStUtJhQkMT/WwJClwT5MlshtKrJPxce5h6vEduZL865CwNx+W3rnVkhiRhaKaQXygeR9eoI7FtgkxKysT8T63dhSG4J1+pgPDk718MoI2K/lii8zI0BUyi2UnQ/9DJtR/MlCQSenS4DWBF8cr/HUlKbpOGkQvFzp7G49HeMwPDQnnAqLf1W2wsW1wSQGQtsdk12g7jKXcsQl5Chpx88hEGYqPJIu78rK9GiZmF2r6TCZV5fzAGE6Jak7d4Y92eglYTuLWDuuSFENlxc4P1cXey4dmnCoLDHI15sDds5+ARxhNZ+Ca6Cnonhh6oaKOAeh1EHWhLPsEdaTO52xLofjmWT8NtXCLknte1wcwD1IzrxPve1awisDxgg3x6zBKDR08zzhyOYULod2JmkRMyffL1TGxzSzzBYIUgtLRV/A6QzhRsPW1F0U8ZkNUNagEXK/u8+p8pmFpo2cFSFZlbbvZMgfkBzpAxvb6hJ6R+5NzT+nfZ3f+J3+Ybj45CLArY0zjJcyDgBgKauFvZDshYzVLjaECAwEAAQKCAgApMpWEoifMAS4hzyqjQqZRs/TEEDRCtcogvtIbQ7id8E5tob/gw5d0icYa0dHOq0EcTcJ1DsXzAVO0Jq9ycS8MMlvDmwO5a6M2rwQfzSmUlj2kZPcBz3BT0paeMHYnEDnhNbpFHW+NnRirRVisOHR08WdbBoyw/jEE3A5P9fM9Q06M9xkBkjsf92FfbAJrALeyr6muTvJbqxAcoQrP5Y/gvfa23I8+DjYfNrBJPKBYlrWvcffUnCCVFXYhEgrlZUXd2ZBvU65amUm+LiZ4D2dzYVL95JLnrOCJWMscFLgHMCElnmlA58fCt80sSYta7t78l+7Ry3A4CmswFw/lJThcZyi32VgXMQRxuzbWBMswZyKEihmIxekTnqhnLDgKtoa+n88dSUc8bvs3YxNYx3Yqy2a+hjxOIHpiT9ikCatoqtpefpspeu/DfKwDZVEBAqC2SliD9QHQlvQtMyLUPqgzDo5df4uSqY8m3QqbIFBC5VJJYEOLFkXJQgYCeUtw4O24VE19YHaNljN/qGbKLtvFR+5QNeObuwZsmYvJieu1h8sUB1TunOSDQdkQgJ11y4Ky1F5wIDzYXQptOgre94PybrZ/exu0HPPcicE4vHT09xiACyNujBDkrfvWaayrPJ0pmfNdHNy9/LjGxglKPbs//AGybMuUnCe99qwJyJhewQKCAQEA74oKT9hER91FlgsS/MUJ0v99tD5C++cYyrqkAS8XX04O1ZSz9fg/ezdrO+F0XHYWCU8QSDjBYqEGd6UHGG7yepJE3VvOagCxKWDm4xjj42OyDdZtLNr2Mb32OCtOhFHb2VSmsp8TTb6PWXg7/W9S84ii5JkIAJbHj1Hb+af2gDxAZIAyRKoI8I9+2xMbrGpJ5qQjvvPZl1j9cvUCGPf8NgSnc6rBJU3NeoY7LtqV5/dcaYewOgZOSqJuqvPlJQP+thOAD9fuUuApiryOA5m8pbdRsS5gVeLL+f1rzqEvon6/lX+fQAxp4Tn515kHIfpSVqWUA6wL1O67SWFV3r14jQKCAQEAv83o8in9G4f7P7ZtbXmp6tV+d692aUayvsZfzKPzHf7WBT4XfdvgMAXrf7YJgeIdmKIe4ppjvste2UKDfgDtS6v0UEOPEGw7+43zlF+oB1jHrEMLvbiLgRmUFI11FaYRtp3dGpGXEqAplJrDhlUBKb0LslJSf2UqxC781gdcnLV0fw583jqS1M57YJu01UGJIunsC5enFKsjuiAeFQ5CJ7SR11n/2x+Z8Rkt4TIZY0RfdEOm6cYc+fCzYJOdalUKiFJPVfB4SKAGILamLK6FCbsNR6sdVGQC/b1CSGjycqlQTC+XJqvBfx5yCjEE/Y0tutuz4cK/Bx6KEaO6fWJ2ZQKCAQEAhmB9Cm+7VklWSSbrPuvWaAy12xB2iVQKP2hWquddCDUE82IZVqouCpR7TrtaiKgiEpTNAIb+TbMhqqrkgRt0Ybh+c2OWNzcuK5VV0R5ccWqzLzoUQu6O4Da90qLQyEAXwiLP5TKCJMH7LujZVoJGGaKUJwOGTrZHOypj6fkEusmSIg8cpBJzM2h8dK+SfbWewYlhGDU54sKkZAH4bENptHAF9EhdU+0CkXKN7unm4JuOtxDMlrCE6S+YP8TUzmAgWsoztC+hXdKs20yNqo1rG9fsNyZaGrRBU3uMJ/2aeGD8XwSaNNcB6ryYYQ9SxgfkewEmOK0ichB+9lppTqwh3QKCAQBaQ5MO9AezfykUcMvKq2j1pQdhV+fH34ebFExdSALP6O/bg78WcfVtZDvR3F9ZCoqXHCSgy7uJPLgkUpMDJ3iFFiVh6IlZVzZbShCJkQLIglVlkh/iZwjv9pOjoHare332jRBSwpVwJIOs9bBydWqYs+jfQLKmVXvs25gNOWWyMgrjCHRnOPWExK93ZY+SlMbu8VsukW4F4gxsOVUu34jumqHP0QEBpDuUJR9cTXF5L+IkGvpgJeXZEhe4qX95XRAZ23KOpR5WP0ji+FH30SG21JqJUdP5tR9bPkgFP65pm5i4YeUmQ8pKAo/0j+EjWd9dgquC4V15AjxD1OOcwkupAoIBAQCDrswYi3ffzsnpqQ1MWrJnjkyB/fL6OuT+m+BLpn4JspLE0JYB/ykoqcv8SKNzDU0NBjozETmlwYVZpI6PAybVvE+ixkcsbOL1yJOI0dY5DcHK3CLI6/kaXtBxPBy7t8hEXvMA+wxO3da4UeDuJW26Y6kw+r5lT9trKrUosH9YvbnwYnW0EAGiSvYtyTq7Pk6r7Hxloeyk0ALeGHmyXUbMETXXGxuGb5LFnAdDTxr9td+moG0GcPvf0VmiB4K1B7/527q2WfKycS9/1nn4qAFPvykuzmQOM368ByDyr17SiYpoN19uDfqZa2caHmVSrhJKhKIBDGzuB/nUYJOnT4fX", //nolint:lll
TLSAuth: "2a081d1a94f133e0c3e1b36ff414f609154e6f2c5586abc2452ec54c70ead6d9f0b5e3b7351eb0eac32d6ddb3d7c24d56cccbf25024bdde1c14d56c02eeb058c3f76ea6798b07955bb38b71dd1d359c93f246b00d624929fcc87d6c34baff5f62f8ac7fa054a3fff8982fc9d1847168ab6a7e2f48c16100cb5865e355f3978f0165cdc9e9217cd49634098c58bda0c15b1ce1ef214604e4f7f1f8b94b93a7791486706f0199973bbe9a6fb462bcb72e4e64263f37653098ddbe02de7b4502c88a4ee7c47cd44bcb3853bde2ccc13dc45fe6b75474f31af57f89cecc1ba6940384de9e41b4abbc38710577fcfc471b4c986b17d72707040378b3cfe57dd4cc372", //nolint:lll
}
+
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
}
diff --git a/internal/provider/purevpn/openvpnconf_test.go b/internal/provider/purevpn/openvpnconf_test.go
new file mode 100644
index 000000000..251c2b871
--- /dev/null
+++ b/internal/provider/purevpn/openvpnconf_test.go
@@ -0,0 +1,98 @@
+package purevpn
+
+import (
+ "net/netip"
+ "strings"
+ "testing"
+
+ "github.com/qdm12/gluetun/internal/configuration/settings"
+ "github.com/qdm12/gluetun/internal/constants"
+ "github.com/qdm12/gluetun/internal/constants/providers"
+ "github.com/qdm12/gluetun/internal/models"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestProviderOpenVPNConfig_UsesBuiltInCryptoMaterial(t *testing.T) {
+ t.Parallel()
+
+ p := Provider{}
+ connection := models.Connection{
+ IP: netip.MustParseAddr("1.2.3.4"),
+ Port: 15021,
+ Protocol: constants.UDP,
+ Hostname: "us2-udp.ptoserver.com",
+ }
+ openvpnSettings := settings.OpenVPN{}.WithDefaults(providers.Purevpn)
+
+ lines := p.OpenVPNConfig(connection, openvpnSettings, false)
+
+ assert.True(t, hasLineContaining(lines, "remote-cert-tls server"))
+ assert.True(t, hasLineContaining(lines, "key-direction 1"))
+ assert.True(t, hasLineContaining(lines, "compress"))
+ assert.True(t, hasLineContaining(lines, "route-method exe"))
+ assert.True(t, hasLineContaining(lines, "route-delay 0"))
+ assert.True(t, hasLineContaining(lines, "script-security 2"))
+ assert.True(t, hasLineContaining(lines, "explicit-exit-notify 2"))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+ assert.True(t, hasLineContaining(lines, ""))
+}
+
+func TestOpenVPNConfig_UsesInventoryPortOnly(t *testing.T) {
+ t.Parallel()
+
+ p := Provider{}
+ connection := models.Connection{
+ IP: netip.MustParseAddr("1.2.3.4"),
+ Port: 1194,
+ Protocol: constants.UDP,
+ }
+
+ lines := p.OpenVPNConfig(connection, testOpenVPNSettings(), true)
+
+ assert.Equal(t, 1, countExactLine(lines, "remote 1.2.3.4 1194"))
+ assert.Zero(t, countExactLine(lines, "remote 1.2.3.4 53"))
+ assert.Zero(t, countExactLine(lines, "remote 1.2.3.4 80"))
+}
+
+func testOpenVPNSettings() settings.OpenVPN {
+ return settings.OpenVPN{
+ User: strPtr(""),
+ Auth: strPtr(""),
+ MSSFix: uint16Ptr(0),
+ Interface: "tun0",
+ ProcessUser: "root",
+ Verbosity: intPtr(1),
+ EncryptedKey: strPtr(""),
+ KeyPassphrase: strPtr(""),
+ Cert: strPtr(""),
+ Key: strPtr(""),
+ }
+}
+
+func strPtr(value string) *string { return &value }
+func uint16Ptr(value uint16) *uint16 { return &value }
+func intPtr(value int) *int { return &value }
+
+func countExactLine(lines []string, target string) (count int) {
+ for _, line := range lines {
+ if line == target {
+ count++
+ }
+ }
+ return count
+}
+
+func hasLineContaining(lines []string, needle string) bool {
+ for _, line := range lines {
+ if strings.Contains(line, needle) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/internal/provider/purevpn/provider.go b/internal/provider/purevpn/provider.go
index f95d16c2f..1bfbdf556 100644
--- a/internal/provider/purevpn/provider.go
+++ b/internal/provider/purevpn/provider.go
@@ -2,6 +2,7 @@ package purevpn
import (
"math/rand"
+ "net/http"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/common"
@@ -15,13 +16,14 @@ type Provider struct {
}
func New(storage common.Storage, randSource rand.Source,
+ client *http.Client,
ipFetcher common.IPFetcher, unzipper common.Unzipper,
updaterWarner common.Warner, parallelResolver common.ParallelResolver,
) *Provider {
return &Provider{
storage: storage,
randSource: randSource,
- Fetcher: updater.New(ipFetcher, unzipper, updaterWarner, parallelResolver),
+ Fetcher: updater.New(client, ipFetcher, unzipper, updaterWarner, parallelResolver),
}
}
diff --git a/internal/provider/purevpn/updater/deb.go b/internal/provider/purevpn/updater/deb.go
new file mode 100644
index 000000000..8e9a1e953
--- /dev/null
+++ b/internal/provider/purevpn/updater/deb.go
@@ -0,0 +1,229 @@
+package updater
+
+import (
+ "archive/tar"
+ "bytes"
+ "compress/gzip"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+
+ "github.com/klauspost/compress/zstd"
+ "github.com/ulikunitz/xz"
+)
+
+const pureVPNAsarPath = "opt/PureVPN/resources/app.asar"
+
+type debEntry struct {
+ name string
+ data []byte
+}
+
+func extractAsarFromDeb(debBytes []byte) (asarContent []byte, err error) {
+ entries, err := parseArArchive(debBytes)
+ if err != nil {
+ return nil, fmt.Errorf("parsing .deb ar archive: %w", err)
+ }
+
+ var dataTarName string
+ var dataTarCompressed []byte
+ for _, entry := range entries {
+ if strings.HasPrefix(entry.name, "data.tar") {
+ dataTarName = entry.name
+ dataTarCompressed = entry.data
+ break
+ }
+ }
+ if len(dataTarCompressed) == 0 {
+ return nil, fmt.Errorf("data.tar archive not found in .deb")
+ }
+
+ dataTar, err := decompressDataTar(dataTarName, dataTarCompressed)
+ if err != nil {
+ return nil, fmt.Errorf("decompressing %s: %w", dataTarName, err)
+ }
+
+ asarContent, err = extractFileFromTar(dataTar, pureVPNAsarPath)
+ if err != nil {
+ return nil, fmt.Errorf("extracting %s from tar: %w", pureVPNAsarPath, err)
+ }
+
+ return asarContent, nil
+}
+
+func parseArArchive(content []byte) (entries []debEntry, err error) {
+ const (
+ globalHeader = "!\n"
+ headerLen = 60
+ )
+
+ if len(content) < len(globalHeader) || string(content[:len(globalHeader)]) != globalHeader {
+ return nil, fmt.Errorf("invalid ar archive header")
+ }
+
+ offset := len(globalHeader)
+ for offset+headerLen <= len(content) {
+ header := content[offset : offset+headerLen]
+ offset += headerLen
+
+ name := strings.TrimSpace(string(header[0:16]))
+ name = strings.TrimSuffix(name, "/")
+
+ sizeString := strings.TrimSpace(string(header[48:58]))
+ size, parseErr := strconv.Atoi(sizeString)
+ if parseErr != nil {
+ return nil, fmt.Errorf("parsing ar member %q size %q: %w", name, sizeString, parseErr)
+ }
+ if size < 0 {
+ return nil, fmt.Errorf("negative size for ar member %q", name)
+ }
+
+ if offset+size > len(content) {
+ return nil, fmt.Errorf("ar member %q overflows archive content", name)
+ }
+ data := make([]byte, size)
+ copy(data, content[offset:offset+size])
+ offset += size
+ if size%2 == 1 {
+ offset++
+ }
+
+ entries = append(entries, debEntry{name: name, data: data})
+ }
+
+ if len(entries) == 0 {
+ return nil, fmt.Errorf("no members found in ar archive")
+ }
+
+ return entries, nil
+}
+
+func decompressDataTar(fileName string, content []byte) (dataTar []byte, err error) {
+ lowerFileName := strings.ToLower(fileName)
+
+ switch {
+ case strings.HasSuffix(lowerFileName, ".xz"):
+ reader, err := xz.NewReader(bytes.NewReader(content))
+ if err != nil {
+ return nil, err
+ }
+ return io.ReadAll(reader)
+ case strings.HasSuffix(lowerFileName, ".gz"):
+ gzipReader, err := gzip.NewReader(bytes.NewReader(content))
+ if err != nil {
+ return nil, err
+ }
+ defer gzipReader.Close()
+ return io.ReadAll(gzipReader)
+ case strings.HasSuffix(lowerFileName, ".zst"):
+ decoder, err := zstd.NewReader(bytes.NewReader(content))
+ if err != nil {
+ return nil, err
+ }
+ defer decoder.Close()
+ return io.ReadAll(decoder)
+ default:
+ return content, nil
+ }
+}
+
+func extractFileFromTar(tarContent []byte, expectedPath string) (fileContent []byte, err error) {
+ tarReader := tar.NewReader(bytes.NewReader(tarContent))
+
+ for {
+ header, err := tarReader.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, fmt.Errorf("reading tar: %w", err)
+ }
+
+ name := strings.TrimPrefix(header.Name, "./")
+ if name != expectedPath {
+ continue
+ }
+
+ fileContent, err = io.ReadAll(tarReader)
+ if err != nil {
+ return nil, fmt.Errorf("reading %s from tar: %w", expectedPath, err)
+ }
+ return fileContent, nil
+ }
+
+ return nil, fmt.Errorf("path %q not found in tar", expectedPath)
+}
+
+type asarNode struct {
+ Files map[string]*asarNode `json:"files,omitempty"`
+ Offset string `json:"offset,omitempty"`
+ Size int `json:"size,omitempty"`
+}
+
+type asarHeader struct {
+ Files map[string]*asarNode `json:"files"`
+}
+
+func extractFileFromAsar(asarContent []byte, targetPath string) (fileContent []byte, err error) {
+ if len(asarContent) < 16 {
+ return nil, fmt.Errorf("asar content too short: %d", len(asarContent))
+ }
+
+ headerLength := int(binary.LittleEndian.Uint32(asarContent[12:16]))
+ if headerLength <= 0 {
+ return nil, fmt.Errorf("invalid asar header length: %d", headerLength)
+ }
+ if 16+headerLength > len(asarContent) {
+ return nil, fmt.Errorf("asar header length exceeds content length")
+ }
+
+ headerContent := asarContent[16 : 16+headerLength]
+ var header asarHeader
+ if err := json.Unmarshal(headerContent, &header); err != nil {
+ return nil, fmt.Errorf("unmarshalling asar header: %w", err)
+ }
+
+ node, err := asarGetNode(header.Files, targetPath)
+ if err != nil {
+ return nil, err
+ }
+
+ offset, err := strconv.Atoi(node.Offset)
+ if err != nil {
+ return nil, fmt.Errorf("parsing asar file offset %q for %q: %w", node.Offset, targetPath, err)
+ }
+ if node.Size < 0 {
+ return nil, fmt.Errorf("negative asar file size %d for %q", node.Size, targetPath)
+ }
+
+ dataOffset := 16 + headerLength + offset
+ dataEnd := dataOffset + node.Size
+ if dataOffset < 0 || dataEnd > len(asarContent) {
+ return nil, fmt.Errorf("asar file %q exceeds content boundaries", targetPath)
+ }
+
+ content := make([]byte, node.Size)
+ copy(content, asarContent[dataOffset:dataEnd])
+ return content, nil
+}
+
+func asarGetNode(files map[string]*asarNode, targetPath string) (node *asarNode, err error) {
+ segments := strings.Split(targetPath, "/")
+ currentFiles := files
+
+ for i, segment := range segments {
+ node = currentFiles[segment]
+ if node == nil {
+ return nil, fmt.Errorf("path %q not found in asar", targetPath)
+ }
+ if i == len(segments)-1 {
+ return node, nil
+ }
+ currentFiles = node.Files
+ }
+
+ return nil, fmt.Errorf("path %q not found in asar", targetPath)
+}
diff --git a/internal/provider/purevpn/updater/download.go b/internal/provider/purevpn/updater/download.go
new file mode 100644
index 000000000..3779f7b7e
--- /dev/null
+++ b/internal/provider/purevpn/updater/download.go
@@ -0,0 +1,191 @@
+package updater
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+const pureVPNLinuxDownloadPageURL = "https://www.purevpn.com/download/linux-vpn"
+
+var (
+ debURLPattern = regexp.MustCompile(`https?://[^"'\s<>]+\.deb`)
+ hrefDebPattern = regexp.MustCompile(`href=["']([^"']+\.deb)["']`)
+ semverPattern = regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`)
+)
+
+type debCandidate struct {
+ url string
+ score int
+ major int
+ minor int
+ patch int
+ position int
+}
+
+func fetchDebURL(ctx context.Context, client *http.Client) (debURL string, err error) {
+ pageContent, err := fetchURL(ctx, client, pureVPNLinuxDownloadPageURL)
+ if err != nil {
+ return "", fmt.Errorf("fetching PureVPN Linux download page: %w", err)
+ }
+
+ debURLs, err := extractDebURLs(string(pageContent), pureVPNLinuxDownloadPageURL)
+ if err != nil {
+ return "", fmt.Errorf("extracting .deb URLs from download page: %w", err)
+ }
+
+ debURL, err = chooseDebURL(debURLs)
+ if err != nil {
+ return "", err
+ }
+ return debURL, nil
+}
+
+func fetchURL(ctx context.Context, client *http.Client, rawURL string) (content []byte, err error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("creating request: %w", err)
+ }
+
+ response, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("performing request: %w", err)
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode < http.StatusOK || response.StatusCode > 299 {
+ return nil, fmt.Errorf("HTTP status code %d", response.StatusCode)
+ }
+
+ content, err = io.ReadAll(response.Body)
+ if err != nil {
+ return nil, fmt.Errorf("reading response body: %w", err)
+ }
+ return content, nil
+}
+
+func extractDebURLs(pageHTML, baseURL string) (debURLs []string, err error) {
+ baseParsed, err := url.Parse(baseURL)
+ if err != nil {
+ return nil, fmt.Errorf("parsing base url %q: %w", baseURL, err)
+ }
+
+ urlsSet := make(map[string]struct{})
+
+ for _, match := range debURLPattern.FindAllString(pageHTML, -1) {
+ urlsSet[match] = struct{}{}
+ }
+
+ for _, groups := range hrefDebPattern.FindAllStringSubmatch(pageHTML, -1) {
+ if len(groups) < 2 {
+ continue
+ }
+ href := strings.TrimSpace(groups[1])
+ parsedHref, parseErr := url.Parse(href)
+ if parseErr != nil {
+ continue
+ }
+ resolved := baseParsed.ResolveReference(parsedHref).String()
+ urlsSet[resolved] = struct{}{}
+ }
+
+ debURLs = make([]string, 0, len(urlsSet))
+ for rawURL := range urlsSet {
+ debURLs = append(debURLs, rawURL)
+ }
+ sort.Strings(debURLs)
+
+ if len(debURLs) == 0 {
+ return nil, fmt.Errorf("no .deb URL found")
+ }
+
+ return debURLs, nil
+}
+
+func chooseDebURL(debURLs []string) (bestURL string, err error) {
+ if len(debURLs) == 0 {
+ return "", fmt.Errorf("no .deb URL candidates")
+ }
+
+ candidates := make([]debCandidate, 0, len(debURLs))
+ for i, debURL := range debURLs {
+ score := scoreDebURL(debURL)
+ major, minor, patch := parseSemverFromURL(debURL)
+ candidates = append(candidates, debCandidate{
+ url: debURL,
+ score: score,
+ major: major,
+ minor: minor,
+ patch: patch,
+ position: i,
+ })
+ }
+
+ sort.Slice(candidates, func(i, j int) bool {
+ left := candidates[i]
+ right := candidates[j]
+ if left.score != right.score {
+ return left.score > right.score
+ }
+ if left.major != right.major {
+ return left.major > right.major
+ }
+ if left.minor != right.minor {
+ return left.minor > right.minor
+ }
+ if left.patch != right.patch {
+ return left.patch > right.patch
+ }
+ if left.position != right.position {
+ return left.position < right.position
+ }
+ return left.url < right.url
+ })
+
+ return candidates[0].url, nil
+}
+
+func scoreDebURL(debURL string) (score int) {
+ lower := strings.ToLower(debURL)
+
+ if strings.Contains(lower, "purevpn") {
+ score += 40
+ }
+ if strings.Contains(lower, "linux") {
+ score += 30
+ }
+ if strings.Contains(lower, "gui") {
+ score += 20
+ }
+ if strings.Contains(lower, "amd64") {
+ score += 20
+ }
+
+ if strings.Contains(lower, "arm") || strings.Contains(lower, "aarch") {
+ score -= 25
+ }
+ if strings.Contains(lower, "i386") || strings.Contains(lower, "x86") {
+ score -= 25
+ }
+
+ return score
+}
+
+func parseSemverFromURL(rawURL string) (major, minor, patch int) {
+ match := semverPattern.FindStringSubmatch(rawURL)
+ if len(match) != 4 {
+ return 0, 0, 0
+ }
+
+ major, _ = strconv.Atoi(match[1])
+ minor, _ = strconv.Atoi(match[2])
+ patch, _ = strconv.Atoi(match[3])
+
+ return major, minor, patch
+}
diff --git a/internal/provider/purevpn/updater/download_test.go b/internal/provider/purevpn/updater/download_test.go
new file mode 100644
index 000000000..20f68672f
--- /dev/null
+++ b/internal/provider/purevpn/updater/download_test.go
@@ -0,0 +1,50 @@
+package updater
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_extractDebURLs(t *testing.T) {
+ t.Parallel()
+
+ const html = `
+
+
+ ignore
+ v2.8.9
+ v2.9.0
+ v2.9.1
+
+ `
+
+ debURLs, err := extractDebURLs(html, "https://www.purevpn.com/download/linux-vpn")
+ require.NoError(t, err)
+
+ assert.Contains(t, debURLs,
+ "https://dhnx3d2u57yhc.cloudfront.net/cross-platform/linux-gui/2.8.9/PureVPN_amd64.deb")
+ assert.Contains(t, debURLs,
+ "https://www.purevpn.com/cross-platform/linux-gui/2.9.0/PureVPN_amd64.deb")
+ assert.Contains(t, debURLs,
+ "https://www.purevpn.com/download/cross-platform/linux-gui/2.9.1/PureVPN_amd64.deb")
+}
+
+func Test_chooseDebURL(t *testing.T) {
+ t.Parallel()
+
+ debURLs := []string{
+ "https://cdn.example.com/cross-platform/linux-gui/2.9.0/PureVPN_arm64.deb",
+ "https://cdn.example.com/cross-platform/linux-cli/2.9.1/PureVPN_amd64.deb",
+ "https://cdn.example.com/cross-platform/linux-gui/2.8.9/PureVPN_amd64.deb",
+ "https://cdn.example.com/cross-platform/linux-gui/2.9.0/PureVPN_amd64.deb",
+ }
+
+ bestURL, err := chooseDebURL(debURLs)
+ require.NoError(t, err)
+
+ assert.Equal(t,
+ "https://cdn.example.com/cross-platform/linux-gui/2.9.0/PureVPN_amd64.deb",
+ bestURL)
+}
diff --git a/internal/provider/purevpn/updater/hosttoserver.go b/internal/provider/purevpn/updater/hosttoserver.go
index 3b6af538f..95a4fc82f 100644
--- a/internal/provider/purevpn/updater/hosttoserver.go
+++ b/internal/provider/purevpn/updater/hosttoserver.go
@@ -9,21 +9,43 @@ import (
type hostToServer map[string]models.Server
-func (hts hostToServer) add(host string, tcp, udp bool) {
+func (hts hostToServer) add(host string, tcp, udp bool, port uint16, p2pTagged bool) {
server, ok := hts[host]
if !ok {
server.VPN = vpn.OpenVPN
server.Hostname = host
}
+ portForward, quantumResistant, obfuscated, p2pInHost := inferPureVPNTraits(host)
+ server.PortForward = server.PortForward || portForward
+ server.QuantumResistant = server.QuantumResistant || quantumResistant
+ server.Obfuscated = server.Obfuscated || obfuscated
+ if p2pTagged || p2pInHost {
+ server.Categories = appendStringIfMissing(server.Categories, "p2p")
+ }
if tcp {
server.TCP = true
+ if port != 0 {
+ server.TCPPorts = appendPortIfMissing(server.TCPPorts, port)
+ }
}
if udp {
server.UDP = true
+ if port != 0 {
+ server.UDPPorts = appendPortIfMissing(server.UDPPorts, port)
+ }
}
hts[host] = server
}
+func appendPortIfMissing(ports []uint16, port uint16) []uint16 {
+ for _, existingPort := range ports {
+ if existingPort == port {
+ return ports
+ }
+ }
+ return append(ports, port)
+}
+
func (hts hostToServer) toHostsSlice() (hosts []string) {
hosts = make([]string, 0, len(hts))
for host := range hts {
diff --git a/internal/provider/purevpn/updater/hosttoserver_test.go b/internal/provider/purevpn/updater/hosttoserver_test.go
new file mode 100644
index 000000000..480fe2022
--- /dev/null
+++ b/internal/provider/purevpn/updater/hosttoserver_test.go
@@ -0,0 +1,42 @@
+package updater
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_hostToServer_add_obfuscationRespectsProtocolAndPort(t *testing.T) {
+ t.Parallel()
+
+ hts := make(hostToServer)
+ hts.add("us2-obf-udp.ptoserver.com", false, true, 1210, false)
+
+ server := hts["us2-obf-udp.ptoserver.com"]
+ assert.True(t, server.Obfuscated)
+ assert.True(t, server.UDP)
+ assert.False(t, server.TCP)
+ assert.Nil(t, server.TCPPorts)
+ assert.Equal(t, []uint16{1210}, server.UDPPorts)
+}
+
+func Test_hostToServer_add_obfuscationTCPUsesInventoryPort(t *testing.T) {
+ t.Parallel()
+
+ hts := make(hostToServer)
+ hts.add("us2-obf-udp.ptoserver.com", true, false, 80, false)
+
+ server := hts["us2-obf-udp.ptoserver.com"]
+ assert.True(t, server.TCP)
+ assert.Equal(t, []uint16{80}, server.TCPPorts)
+}
+
+func Test_hostToServer_add_p2pTagSetsCategory(t *testing.T) {
+ t.Parallel()
+
+ hts := make(hostToServer)
+ hts.add("us2-udp.ptoserver.com", false, true, 15021, true)
+
+ server := hts["us2-udp.ptoserver.com"]
+ assert.Equal(t, []string{"p2p"}, server.Categories)
+}
diff --git a/internal/provider/purevpn/updater/inventory.go b/internal/provider/purevpn/updater/inventory.go
new file mode 100644
index 000000000..8a71fe2d0
--- /dev/null
+++ b/internal/provider/purevpn/updater/inventory.go
@@ -0,0 +1,256 @@
+package updater
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/netip"
+ "regexp"
+ "strings"
+)
+
+const (
+ inventoryEndpointsAsarPath = "node_modules/atom-sdk/node_modules/utils/lib/constants/end-points.js"
+ inventoryOfflineAsarPath = "node_modules/atom-sdk/node_modules/inventory/lib/offline-data/inventory-data.js"
+)
+
+var (
+ baseURLBPCRegex = regexp.MustCompile(`BASE_URL_BPC"\s*,\s*"([^"]+)"`)
+ inventoryPathRegex = regexp.MustCompile(`"/\{resellerUid\}[^"]*app\.json"`)
+ resellerUIDRegexJSON = regexp.MustCompile(`Uid"\s*:\s*"([^"]+)"`)
+ resellerUIDRegexJS = regexp.MustCompile(`Uid\s*:\s*"([^"]+)"`)
+)
+
+func parseInventoryURLTemplate(endpointsJS []byte) (template string, err error) {
+ raw := string(endpointsJS)
+
+ baseMatch := baseURLBPCRegex.FindStringSubmatch(raw)
+ if len(baseMatch) != 2 {
+ return "", fmt.Errorf("BASE_URL_BPC not found in endpoints file")
+ }
+ baseURL := strings.TrimSpace(baseMatch[1])
+ if baseURL == "" {
+ return "", fmt.Errorf("BASE_URL_BPC is empty")
+ }
+
+ pathMatch := inventoryPathRegex.FindString(raw)
+ if pathMatch == "" {
+ return "", fmt.Errorf("inventory path not found in endpoints file")
+ }
+ // Strip surrounding quotes from the JS string literal.
+ path := strings.Trim(pathMatch, `"`)
+ return strings.TrimRight(baseURL, "/") + path, nil
+}
+
+func parseResellerUIDFromInventoryOffline(offlineInventoryJS []byte) (resellerUID string, err error) {
+ raw := string(offlineInventoryJS)
+
+ match := resellerUIDRegexJSON.FindStringSubmatch(raw)
+ if len(match) != 2 {
+ match = resellerUIDRegexJS.FindStringSubmatch(raw)
+ }
+ if len(match) != 2 {
+ return "", fmt.Errorf("reseller Uid not found in inventory offline data")
+ }
+ resellerUID = strings.TrimSpace(match[1])
+ if resellerUID == "" {
+ return "", fmt.Errorf("reseller Uid is empty")
+ }
+ return resellerUID, nil
+}
+
+func buildInventoryURL(template, resellerUID string) (inventoryURL string, err error) {
+ if template == "" {
+ return "", fmt.Errorf("inventory URL template is empty")
+ }
+ if resellerUID == "" {
+ return "", fmt.Errorf("reseller UID is empty")
+ }
+ if !strings.Contains(template, "{resellerUid}") {
+ return "", fmt.Errorf("inventory URL template does not contain {resellerUid}")
+ }
+ return strings.Replace(template, "{resellerUid}", resellerUID, 1), nil
+}
+
+type inventoryResponse struct {
+ Body inventoryBody `json:"body"`
+}
+
+type inventoryBody struct {
+ Countries []inventoryCountry `json:"countries"`
+ DNS []inventoryDNS `json:"dns"`
+ DataCenters []inventoryDataCenter `json:"data_centers"`
+}
+
+type inventoryCountry struct {
+ DataCenters []inventoryDataCenterRef `json:"data_centers"`
+ Protocols []inventoryProtocol `json:"protocols"`
+ Features []string `json:"features"`
+}
+
+type inventoryDataCenterRef struct {
+ ID int `json:"id"`
+}
+
+type inventoryProtocol struct {
+ Protocol string `json:"protocol"`
+ DNS []inventoryProtocolDNS `json:"dns"`
+}
+
+type inventoryProtocolDNS struct {
+ DNSID int `json:"dns_id"`
+ PortNumber int `json:"port_number"`
+}
+
+type inventoryDNS struct {
+ ID int `json:"id"`
+ Hostname string `json:"hostname"`
+ ConfigurationVersion string `json:"configuration_version"`
+ Tags []string `json:"tags"`
+}
+
+type inventoryDataCenter struct {
+ ID int `json:"id"`
+ IP string `json:"ip"`
+}
+
+func parseInventoryJSON(content []byte) (hts hostToServer, hostToFallbackIPs map[string][]netip.Addr, err error) {
+ var response inventoryResponse
+ if err := json.Unmarshal(content, &response); err != nil {
+ return nil, nil, fmt.Errorf("unmarshalling inventory JSON: %w", err)
+ }
+
+ if len(response.Body.Countries) == 0 {
+ return nil, nil, fmt.Errorf("no countries found in inventory JSON")
+ }
+
+ dnsIDToHostname := make(map[int]string, len(response.Body.DNS))
+ dnsIDToP2PTagged := make(map[int]bool, len(response.Body.DNS))
+ for _, dnsEntry := range response.Body.DNS {
+ if dnsEntry.ID == 0 || dnsEntry.Hostname == "" {
+ continue
+ }
+ dnsIDToHostname[dnsEntry.ID] = strings.TrimSpace(dnsEntry.Hostname)
+ dnsIDToP2PTagged[dnsEntry.ID] = hasP2PTag(dnsEntry.Tags)
+ }
+
+ dataCenterIDToIP := make(map[int]netip.Addr, len(response.Body.DataCenters))
+ for _, dataCenter := range response.Body.DataCenters {
+ if dataCenter.ID == 0 || dataCenter.IP == "" {
+ continue
+ }
+ ip, parseErr := netip.ParseAddr(strings.TrimSpace(dataCenter.IP))
+ if parseErr != nil {
+ continue
+ }
+ dataCenterIDToIP[dataCenter.ID] = ip
+ }
+
+ hts = make(hostToServer)
+ hostToFallbackIPs = make(map[string][]netip.Addr)
+ blocksFound := 0
+
+ for _, country := range response.Body.Countries {
+ countryP2PTagged := hasP2PTag(country.Features)
+ countryDataCenterIPs := make([]netip.Addr, 0, len(country.DataCenters))
+ for _, dataCenterRef := range country.DataCenters {
+ ip, ok := dataCenterIDToIP[dataCenterRef.ID]
+ if !ok {
+ continue
+ }
+ countryDataCenterIPs = appendIPIfMissing(countryDataCenterIPs, ip)
+ }
+
+ for _, protocol := range country.Protocols {
+ protocolName := strings.ToUpper(protocol.Protocol)
+ tcp := protocolName == "TCP"
+ udp := protocolName == "UDP"
+ if !tcp && !udp {
+ continue
+ }
+ blocksFound++
+
+ for _, dns := range protocol.DNS {
+ hostname := strings.TrimSpace(dnsIDToHostname[dns.DNSID])
+ if hostname == "" {
+ continue
+ }
+
+ port := uint16(0)
+ if dns.PortNumber > 0 && dns.PortNumber <= 65535 {
+ port = uint16(dns.PortNumber)
+ }
+ p2pTagged := countryP2PTagged || dnsIDToP2PTagged[dns.DNSID]
+ hts.add(hostname, tcp, udp, port, p2pTagged)
+
+ for _, ip := range countryDataCenterIPs {
+ hostToFallbackIPs[hostname] = appendIPIfMissing(hostToFallbackIPs[hostname], ip)
+ }
+ }
+ }
+ }
+
+ if blocksFound == 0 {
+ return nil, nil, fmt.Errorf("no TCP/UDP protocol blocks found in inventory JSON")
+ }
+ if len(hts) == 0 {
+ return nil, nil, fmt.Errorf("no OpenVPN TCP/UDP DNS hosts found in inventory JSON")
+ }
+
+ return hts, hostToFallbackIPs, nil
+}
+
+func parseInventoryConfigurationVersions(content []byte) (versions []string, err error) {
+ var response inventoryResponse
+ if err := json.Unmarshal(content, &response); err != nil {
+ return nil, fmt.Errorf("unmarshalling inventory JSON: %w", err)
+ }
+
+ set := make(map[string]struct{})
+ for _, dnsEntry := range response.Body.DNS {
+ version := strings.TrimSpace(dnsEntry.ConfigurationVersion)
+ if version == "" {
+ continue
+ }
+ if _, exists := set[version]; exists {
+ continue
+ }
+ set[version] = struct{}{}
+ versions = append(versions, version)
+ }
+
+ return versions, nil
+}
+
+func hasP2PTag(tags []string) (p2p bool) {
+ separatorNormalizer := strings.NewReplacer("-", "_", " ", "_")
+ for _, tag := range tags {
+ normalized := strings.ToLower(strings.TrimSpace(tag))
+ if normalized == "" {
+ continue
+ }
+ normalized = separatorNormalizer.Replace(normalized)
+ for _, token := range strings.Split(normalized, "_") {
+ if token == "p2p" {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func extractFirstFileFromAsar(asarContent []byte, paths ...string) (content []byte, usedPath string, err error) {
+ if len(paths) == 0 {
+ return nil, "", fmt.Errorf("no asar paths provided")
+ }
+
+ var lastErr error
+ for _, path := range paths {
+ content, err = extractFileFromAsar(asarContent, path)
+ if err == nil {
+ return content, path, nil
+ }
+ lastErr = err
+ }
+
+ return nil, "", fmt.Errorf("extracting from app.asar failed for paths %q: %w", paths, lastErr)
+}
diff --git a/internal/provider/purevpn/updater/inventory_test.go b/internal/provider/purevpn/updater/inventory_test.go
new file mode 100644
index 000000000..c1104dea6
--- /dev/null
+++ b/internal/provider/purevpn/updater/inventory_test.go
@@ -0,0 +1,117 @@
+package updater
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_parseInventoryURLTemplate(t *testing.T) {
+ t.Parallel()
+
+ content := []byte(`"use strict";
+(0,_defineProperty2["default"])(S3, "BASE_URL_BPC", "https://bpc-prod-a230.s3.serverwild.com/bpc");
+(0,_defineProperty2["default"])(S3, "INVENTORY_URL", "".concat(S3.BASE_URL_BPC, "/{resellerUid}/inventory/shared/linux/v3/app.json"));`)
+
+ template, err := parseInventoryURLTemplate(content)
+ require.NoError(t, err)
+
+ assert.Equal(t,
+ "https://bpc-prod-a230.s3.serverwild.com/bpc/{resellerUid}/inventory/shared/linux/v3/app.json",
+ template)
+}
+
+func Test_buildInventoryURL(t *testing.T) {
+ t.Parallel()
+
+ url, err := buildInventoryURL(
+ "https://bpc-prod-a230.s3.serverwild.com/bpc/{resellerUid}/inventory/shared/linux/v3/app.json",
+ "res_abc")
+ require.NoError(t, err)
+ assert.Equal(t, "https://bpc-prod-a230.s3.serverwild.com/bpc/res_abc/inventory/shared/linux/v3/app.json", url)
+}
+
+func Test_parseInventoryJSON(t *testing.T) {
+ t.Parallel()
+
+ content := []byte(`{
+ "body":{
+ "data_centers":[
+ {"id":10,"ip":"1.2.3.4"},
+ {"id":11,"ip":"5.6.7.8"}
+ ],
+ "dns":[
+ {"id":101,"hostname":"aa2-tcp.ptoserver.com","type":"primary","configuration_version":"2.0","tags":["p2p"]},
+ {"id":102,"hostname":"aa2-udp.ptoserver.com","type":"primary","configuration_version":"14.0"}
+ ],
+ "countries":[
+ {
+ "features":["p2p"],
+ "data_centers":[{"id":10},{"id":11}],
+ "protocols":[
+ {"protocol":"TCP","dns":[{"dns_id":101,"port_number":80}]},
+ {"protocol":"UDP","dns":[{"dns_id":102,"port_number":15021}]}
+ ]
+ }
+ ]
+ }
+ }`)
+
+ hts, hostToFallbackIPs, err := parseInventoryJSON(content)
+ require.NoError(t, err)
+
+ serverTCP := hts["aa2-tcp.ptoserver.com"]
+ assert.True(t, serverTCP.TCP)
+ assert.False(t, serverTCP.UDP)
+ assert.Equal(t, []uint16{80}, serverTCP.TCPPorts)
+ assert.Nil(t, serverTCP.UDPPorts)
+ assert.Equal(t, []string{"p2p"}, serverTCP.Categories)
+
+ serverUDP := hts["aa2-udp.ptoserver.com"]
+ assert.True(t, serverUDP.UDP)
+ assert.False(t, serverUDP.TCP)
+ assert.Equal(t, []uint16{15021}, serverUDP.UDPPorts)
+ assert.Nil(t, serverUDP.TCPPorts)
+ assert.Equal(t, []string{"p2p"}, serverUDP.Categories)
+
+ assert.Equal(t, []netip.Addr{
+ netip.MustParseAddr("1.2.3.4"),
+ netip.MustParseAddr("5.6.7.8"),
+ }, hostToFallbackIPs["aa2-tcp.ptoserver.com"])
+
+ assert.Equal(t, []netip.Addr{
+ netip.MustParseAddr("1.2.3.4"),
+ netip.MustParseAddr("5.6.7.8"),
+ }, hostToFallbackIPs["aa2-udp.ptoserver.com"])
+}
+
+func Test_parseInventoryConfigurationVersions(t *testing.T) {
+ t.Parallel()
+
+ content := []byte(`{
+ "body":{
+ "dns":[
+ {"id":101,"hostname":"aa2-tcp.ptoserver.com","configuration_version":"2.0"},
+ {"id":102,"hostname":"aa2-udp.ptoserver.com","configuration_version":"14.0"},
+ {"id":103,"hostname":"aa3-udp.ptoserver.com","configuration_version":"14.0"},
+ {"id":104,"hostname":"aa4-udp.ptoserver.com","configuration_version":""}
+ ]
+ }
+ }`)
+
+ versions, err := parseInventoryConfigurationVersions(content)
+ assert.NoError(t, err)
+ assert.ElementsMatch(t, []string{"2.0", "14.0"}, versions)
+}
+
+func Test_hasP2PTag(t *testing.T) {
+ t.Parallel()
+
+ assert.True(t, hasP2PTag([]string{"p2p"}))
+ assert.True(t, hasP2PTag([]string{"TAG_P2P"}))
+ assert.True(t, hasP2PTag([]string{"tag-p2p"}))
+ assert.True(t, hasP2PTag([]string{"tag p2p"}))
+ assert.False(t, hasP2PTag([]string{"TAG_QR", "TAG_OVPN_OBF"}))
+}
diff --git a/internal/provider/purevpn/updater/localdata.go b/internal/provider/purevpn/updater/localdata.go
new file mode 100644
index 000000000..3136e6104
--- /dev/null
+++ b/internal/provider/purevpn/updater/localdata.go
@@ -0,0 +1,338 @@
+package updater
+
+import (
+ "fmt"
+ "net/netip"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+const localDataAsarPath = "node_modules/atom-sdk/lib/offline-data/local-data.js"
+
+var (
+ hostNeedle = `name:"`
+ portNeedle = `port_number:`
+ idPattern = regexp.MustCompile(`id:\s*"?([0-9]+)"?`)
+)
+
+func parseLocalData(content []byte) (hostToServer, error) {
+ raw := string(content)
+ hts := make(hostToServer)
+ const protocolNeedle = `protocol:"`
+ blocksFound := 0
+ for index := 0; index < len(raw); {
+ start := strings.Index(raw[index:], protocolNeedle)
+ if start == -1 {
+ break
+ }
+ start += index
+ protocolStart := start + len(protocolNeedle)
+ protocolEnd := strings.IndexByte(raw[protocolStart:], '"')
+ if protocolEnd == -1 {
+ break
+ }
+ protocolEnd += protocolStart
+ protocol := strings.ToUpper(raw[protocolStart:protocolEnd])
+ tcp := protocol == "TCP"
+ udp := protocol == "UDP"
+ index = protocolEnd + 1
+ if !tcp && !udp {
+ continue
+ }
+ blocksFound++
+
+ dnsMarker := strings.Index(raw[index:], `dns:[`)
+ if dnsMarker == -1 {
+ continue
+ }
+ dnsMarker += index
+ arrayStart := dnsMarker + len(`dns:`)
+ dnsArray, arrayEnd, err := extractBracketContent(raw, arrayStart, '[', ']')
+ if err != nil {
+ continue
+ }
+ index = arrayEnd + 1
+
+ for _, entry := range splitObjectEntries(dnsArray) {
+ host, port, ok := parseHostPortFromDNSEntry(entry)
+ if !ok {
+ continue
+ }
+ hts.add(host, tcp, udp, port, false)
+ }
+ }
+
+ if blocksFound == 0 {
+ return nil, fmt.Errorf("no TCP/UDP protocol blocks found in local-data payload")
+ }
+ if len(hts) == 0 {
+ return nil, fmt.Errorf("no OpenVPN TCP/UDP DNS hosts found in local-data payload")
+ }
+
+ return hts, nil
+}
+
+func extractBracketContent(s string, openIndex int, open, close byte) (content string, closeIndex int, err error) {
+ if openIndex < 0 || openIndex >= len(s) || s[openIndex] != open {
+ return "", -1, fmt.Errorf("opening bracket not found at index %d", openIndex)
+ }
+ depth := 0
+ for i := openIndex; i < len(s); i++ {
+ switch s[i] {
+ case open:
+ depth++
+ case close:
+ depth--
+ if depth == 0 {
+ return s[openIndex+1 : i], i, nil
+ }
+ }
+ }
+ return "", -1, fmt.Errorf("closing bracket not found for index %d", openIndex)
+}
+
+func splitObjectEntries(s string) (entries []string) {
+ entryStart := -1
+ depth := 0
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '{':
+ if depth == 0 {
+ entryStart = i
+ }
+ depth++
+ case '}':
+ if depth == 0 {
+ continue
+ }
+ depth--
+ if depth == 0 && entryStart >= 0 {
+ entries = append(entries, s[entryStart:i+1])
+ entryStart = -1
+ }
+ }
+ }
+ return entries
+}
+
+func parseHostPortFromDNSEntry(entry string) (host string, port uint16, ok bool) {
+ hostStart := strings.Index(entry, hostNeedle)
+ if hostStart == -1 {
+ return "", 0, false
+ }
+ hostStart += len(hostNeedle)
+ hostEnd := strings.IndexByte(entry[hostStart:], '"')
+ if hostEnd == -1 {
+ return "", 0, false
+ }
+ hostEnd += hostStart
+ host = strings.TrimSpace(entry[hostStart:hostEnd])
+ if host == "" {
+ return "", 0, false
+ }
+
+ portStart := strings.Index(entry, portNeedle)
+ if portStart == -1 {
+ return "", 0, false
+ }
+ portStart += len(portNeedle)
+ portEnd := portStart
+ for ; portEnd < len(entry) && entry[portEnd] >= '0' && entry[portEnd] <= '9'; portEnd++ {
+ }
+ if portEnd == portStart {
+ return "", 0, false
+ }
+ port64, err := strconv.ParseUint(entry[portStart:portEnd], 10, 16)
+ if err != nil || port64 == 0 {
+ return "", 0, false
+ }
+ return host, uint16(port64), true
+}
+
+func parseLocalDataFallbackIPs(content []byte) (hostToFallbackIPs map[string][]netip.Addr) {
+ raw := string(content)
+
+ dataCenterIDToIP := parseDataCenterIDToPingIP(raw)
+ if len(dataCenterIDToIP) == 0 {
+ return nil
+ }
+
+ countriesArray, _, err := extractArrayByKey(raw, "countries:")
+ if err != nil {
+ return nil
+ }
+
+ hostToFallbackIPs = make(map[string][]netip.Addr)
+ for _, countryEntry := range splitObjectEntries(countriesArray) {
+ dataCenterIDs := parseCountryDataCenterIDs(countryEntry)
+ if len(dataCenterIDs) == 0 {
+ continue
+ }
+
+ hosts := parseTCPUDPHostsFromChunk(countryEntry)
+ if len(hosts) == 0 {
+ continue
+ }
+
+ for _, host := range hosts {
+ for _, dataCenterID := range dataCenterIDs {
+ ip, ok := dataCenterIDToIP[dataCenterID]
+ if !ok {
+ continue
+ }
+ hostToFallbackIPs[host] = appendIPIfMissing(hostToFallbackIPs[host], ip)
+ }
+ }
+ }
+
+ return hostToFallbackIPs
+}
+
+func parseDataCenterIDToPingIP(raw string) map[int]netip.Addr {
+ dataCentersArray, _, err := extractArrayByKey(raw, "data_centers:")
+ if err != nil {
+ return nil
+ }
+
+ dataCenterIDToIP := make(map[int]netip.Addr)
+ for _, dataCenterEntry := range splitObjectEntries(dataCentersArray) {
+ id := parseID(dataCenterEntry)
+ if id == 0 {
+ continue
+ }
+ pingIP, ok := parseQuotedValue(dataCenterEntry, "ping_ip_address:")
+ if !ok || pingIP == "" {
+ continue
+ }
+ ip, err := netip.ParseAddr(pingIP)
+ if err != nil {
+ continue
+ }
+ dataCenterIDToIP[id] = ip
+ }
+ return dataCenterIDToIP
+}
+
+func parseCountryDataCenterIDs(countryEntry string) (ids []int) {
+ dataCentersArray, _, err := extractArrayByKey(countryEntry, "data_centers:")
+ if err != nil {
+ return nil
+ }
+ for _, dataCenterEntry := range splitObjectEntries(dataCentersArray) {
+ id := parseID(dataCenterEntry)
+ if id == 0 {
+ continue
+ }
+ ids = appendIntIfMissing(ids, id)
+ }
+ return ids
+}
+
+func parseTCPUDPHostsFromChunk(chunk string) (hosts []string) {
+ const protocolNeedle = `protocol:"`
+ for index := 0; index < len(chunk); {
+ start := strings.Index(chunk[index:], protocolNeedle)
+ if start == -1 {
+ break
+ }
+ start += index
+ protocolStart := start + len(protocolNeedle)
+ protocolEnd := strings.IndexByte(chunk[protocolStart:], '"')
+ if protocolEnd == -1 {
+ break
+ }
+ protocolEnd += protocolStart
+ protocol := strings.ToUpper(chunk[protocolStart:protocolEnd])
+ index = protocolEnd + 1
+ if protocol != "TCP" && protocol != "UDP" {
+ continue
+ }
+
+ dnsArray, arrayEnd, err := extractArrayByKey(chunk[index:], "dns:")
+ if err != nil {
+ continue
+ }
+ index += arrayEnd + 1
+
+ for _, entry := range splitObjectEntries(dnsArray) {
+ host, _, ok := parseHostPortFromDNSEntry(entry)
+ if !ok {
+ continue
+ }
+ hosts = appendStringIfMissing(hosts, host)
+ }
+ }
+ return hosts
+}
+
+func extractArrayByKey(s, key string) (content string, endIndex int, err error) {
+ keyIndex := strings.Index(s, key)
+ if keyIndex == -1 {
+ return "", -1, fmt.Errorf("key %q not found", key)
+ }
+ openIndex := strings.IndexByte(s[keyIndex:], '[')
+ if openIndex == -1 {
+ return "", -1, fmt.Errorf("array opening bracket not found for key %q", key)
+ }
+ openIndex += keyIndex
+ content, closeIndex, err := extractBracketContent(s, openIndex, '[', ']')
+ if err != nil {
+ return "", -1, err
+ }
+ return content, closeIndex, nil
+}
+
+func parseQuotedValue(s, key string) (value string, ok bool) {
+ keyIndex := strings.Index(s, key)
+ if keyIndex == -1 {
+ return "", false
+ }
+ start := strings.IndexByte(s[keyIndex:], '"')
+ if start == -1 {
+ return "", false
+ }
+ start += keyIndex + 1
+ end := strings.IndexByte(s[start:], '"')
+ if end == -1 {
+ return "", false
+ }
+ end += start
+ return strings.TrimSpace(s[start:end]), true
+}
+
+func parseID(s string) (id int) {
+ match := idPattern.FindStringSubmatch(s)
+ if len(match) < 2 {
+ return 0
+ }
+ id, _ = strconv.Atoi(match[1])
+ return id
+}
+
+func appendStringIfMissing(values []string, value string) []string {
+ for _, existing := range values {
+ if existing == value {
+ return values
+ }
+ }
+ return append(values, value)
+}
+
+func appendIntIfMissing(values []int, value int) []int {
+ for _, existing := range values {
+ if existing == value {
+ return values
+ }
+ }
+ return append(values, value)
+}
+
+func appendIPIfMissing(values []netip.Addr, value netip.Addr) []netip.Addr {
+ for _, existing := range values {
+ if existing.Compare(value) == 0 {
+ return values
+ }
+ }
+ return append(values, value)
+}
diff --git a/internal/provider/purevpn/updater/localdata_test.go b/internal/provider/purevpn/updater/localdata_test.go
new file mode 100644
index 000000000..e969d34ba
--- /dev/null
+++ b/internal/provider/purevpn/updater/localdata_test.go
@@ -0,0 +1,79 @@
+package updater
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_parseLocalData(t *testing.T) {
+ t.Parallel()
+
+ content := []byte(`"use strict";module.exports={body:{countries:[{id:1,protocols:[
+ {number:8,protocol:"TCP",dns:[
+ {name:"us2-tcp.ptoserver.com",port_number:80},
+ {name:"us2-alt.ptoserver.com",port_number:8080}
+ ]},
+ {number:9,protocol:"UDP",dns:[
+ {name:"us2-udp.ptoserver.com",port_number:15021},
+ {name:"us2-obf-udp.ptoserver.com",port_number:1210}
+ ]}
+ ]}]}};`)
+
+ hts, err := parseLocalData(content)
+ require.NoError(t, err)
+
+ serverTCP := hts["us2-tcp.ptoserver.com"]
+ assert.True(t, serverTCP.TCP)
+ assert.False(t, serverTCP.UDP)
+ assert.Equal(t, []uint16{80}, serverTCP.TCPPorts)
+ assert.Nil(t, serverTCP.UDPPorts)
+
+ serverUDP := hts["us2-udp.ptoserver.com"]
+ assert.True(t, serverUDP.UDP)
+ assert.False(t, serverUDP.TCP)
+ assert.Equal(t, []uint16{15021}, serverUDP.UDPPorts)
+ assert.Nil(t, serverUDP.TCPPorts)
+}
+
+func Test_parseLocalData_noHosts(t *testing.T) {
+ t.Parallel()
+
+ _, err := parseLocalData([]byte(`"use strict";module.exports={body:{countries:[{id:1,protocols:[{protocol:"IKEV",dns:[]}]}]}};`))
+
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "no TCP/UDP protocol blocks")
+}
+
+func Test_parseLocalDataFallbackIPs(t *testing.T) {
+ t.Parallel()
+
+ content := []byte(`"use strict";module.exports={body:{
+ data_centers:[
+ {id:10,ping_ip_address:"1.2.3.4"},
+ {id:11,ping_ip_address:"5.6.7.8"}
+ ],
+ countries:[{
+ id:1,
+ data_centers:[{id:10},{id:11}],
+ protocols:[
+ {protocol:"TCP",dns:[{name:"aa2-tcp.ptoserver.com",port_number:80}]},
+ {protocol:"UDP",dns:[{name:"aa2-udp.ptoserver.com",port_number:15021}]}
+ ]
+ }]
+ }};`)
+
+ hostToFallbackIPs := parseLocalDataFallbackIPs(content)
+ require.NotEmpty(t, hostToFallbackIPs)
+
+ assert.Equal(t, []netip.Addr{
+ netip.MustParseAddr("1.2.3.4"),
+ netip.MustParseAddr("5.6.7.8"),
+ }, hostToFallbackIPs["aa2-tcp.ptoserver.com"])
+ assert.Equal(t, []netip.Addr{
+ netip.MustParseAddr("1.2.3.4"),
+ netip.MustParseAddr("5.6.7.8"),
+ }, hostToFallbackIPs["aa2-udp.ptoserver.com"])
+}
diff --git a/internal/provider/purevpn/updater/parse.go b/internal/provider/purevpn/updater/parse.go
index 5e54ed752..c90b5134f 100644
--- a/internal/provider/purevpn/updater/parse.go
+++ b/internal/provider/purevpn/updater/parse.go
@@ -13,7 +13,12 @@ var countryCodeToName = constants.CountryCodes() //nolint:gochecknoglobals
var countryCityCodeToCityName = map[string]string{
"aume": "Melbourne",
"aupe": "Perth",
+ "aubb": "Brisbane",
+ "aubn": "Brisbane",
"ausd": "Sydney",
+ "caq": "Montreal",
+ "cato": "Toronto",
+ "cav": "Vancouver",
"ukl": "London",
"ukm": "Manchester",
"usca": "Los Angeles",
diff --git a/internal/provider/purevpn/updater/parse_test.go b/internal/provider/purevpn/updater/parse_test.go
new file mode 100644
index 000000000..3af5dc271
--- /dev/null
+++ b/internal/provider/purevpn/updater/parse_test.go
@@ -0,0 +1,58 @@
+package updater
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_parseHostname_CanadaCityCodes(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ hostname string
+ country string
+ city string
+ }{
+ "country only no city code": {
+ hostname: "ca2-auto-udp.ptoserver.com",
+ country: "Canada",
+ city: "",
+ },
+ "single-letter city code q": {
+ hostname: "caq2-auto-udp.ptoserver.com",
+ country: "Canada",
+ city: "Montreal",
+ },
+ "australia brisbane code bb": {
+ hostname: "aubb2-auto-udp.ptoserver.com",
+ country: "Australia",
+ city: "Brisbane",
+ },
+ "australia brisbane code bn": {
+ hostname: "aubn2-auto-udp.ptoserver.com",
+ country: "Australia",
+ city: "Brisbane",
+ },
+ "single-letter city code v": {
+ hostname: "cav2-auto-udp.ptoserver.com",
+ country: "Canada",
+ city: "Vancouver",
+ },
+ "two-letter city code to": {
+ hostname: "cato2-auto-udp.ptoserver.com",
+ country: "Canada",
+ city: "Toronto",
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ country, city, warnings := parseHostname(testCase.hostname)
+ assert.Equal(t, testCase.country, country)
+ assert.Equal(t, testCase.city, city)
+ assert.Empty(t, warnings)
+ })
+ }
+}
diff --git a/internal/provider/purevpn/updater/resolve.go b/internal/provider/purevpn/updater/resolve.go
index d0b3fa0c7..0620cd319 100644
--- a/internal/provider/purevpn/updater/resolve.go
+++ b/internal/provider/purevpn/updater/resolve.go
@@ -8,7 +8,7 @@ import (
func parallelResolverSettings(hosts []string) (settings resolver.ParallelSettings) {
const (
- maxFailRatio = 0.1
+ maxFailRatio = 1.0
maxDuration = 20 * time.Second
betweenDuration = time.Second
maxNoNew = 2
diff --git a/internal/provider/purevpn/updater/servers.go b/internal/provider/purevpn/updater/servers.go
index 3d40ec834..e186e4dce 100644
--- a/internal/provider/purevpn/updater/servers.go
+++ b/internal/provider/purevpn/updater/servers.go
@@ -9,52 +9,79 @@ import (
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
- "github.com/qdm12/gluetun/internal/publicip/api"
- "github.com/qdm12/gluetun/internal/updater/openvpn"
+ "github.com/qdm12/gluetun/internal/updater/resolver"
)
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
servers []models.Server, err error,
) {
- if !u.ipFetcher.CanFetchAnyIP() {
- return nil, fmt.Errorf("%w: %s", common.ErrIPFetcherUnsupported, u.ipFetcher.String())
+ debURL, err := fetchDebURL(ctx, u.client)
+ if err != nil {
+ return nil, fmt.Errorf("fetching .deb URL: %w", err)
}
- const url = "https://d11a57lttb2ffq.cloudfront.net/heartbleed/router/Recommended-CA2.zip"
- contents, err := u.unzipper.FetchAndExtract(ctx, url)
+ debContent, err := fetchURL(ctx, u.client, debURL)
if err != nil {
- return nil, err
- } else if len(contents) < minServers {
- return nil, fmt.Errorf("%w: %d and expected at least %d",
- common.ErrNotEnoughServers, len(contents), minServers)
+ return nil, fmt.Errorf("fetching PureVPN .deb file %q: %w", debURL, err)
}
- hts := make(hostToServer)
+ asarContent, err := extractAsarFromDeb(debContent)
+ if err != nil {
+ return nil, fmt.Errorf("extracting app.asar from .deb: %w", err)
+ }
- for fileName, content := range contents {
- if !strings.HasSuffix(fileName, ".ovpn") {
- continue
- }
+ endpointsContent, endpointsPath, err := extractFirstFileFromAsar(asarContent,
+ inventoryEndpointsAsarPath,
+ "node_modules/atom-sdk/node_modules/inventory/node_modules/utils/lib/constants/end-points.js")
+ if err != nil {
+ return nil, fmt.Errorf("extracting inventory endpoints file from app.asar: %w", err)
+ }
- tcp, udp, err := openvpn.ExtractProto(content)
- if err != nil {
- // treat error as warning and go to next file
- u.warner.Warn(err.Error() + " in " + fileName)
- continue
- }
+ inventoryURLTemplate, err := parseInventoryURLTemplate(endpointsContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing inventory URL template from %q: %w", endpointsPath, err)
+ }
- host, warning, err := openvpn.ExtractHost(content)
- if warning != "" {
- u.warner.Warn(warning)
- }
+ offlineInventoryContent, offlineInventoryPath, err := extractFirstFileFromAsar(asarContent,
+ inventoryOfflineAsarPath,
+ "node_modules/atom-sdk/node_modules/inventory/src/offline-data/inventory-data.js")
+ if err != nil {
+ return nil, fmt.Errorf("extracting inventory offline data from app.asar: %w", err)
+ }
- if err != nil {
- // treat error as warning and go to next file
- u.warner.Warn(err.Error() + " in " + fileName)
- continue
+ resellerUID, err := parseResellerUIDFromInventoryOffline(offlineInventoryContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing reseller UID from %q: %w", offlineInventoryPath, err)
+ }
+
+ inventoryURL, err := buildInventoryURL(inventoryURLTemplate, resellerUID)
+ if err != nil {
+ return nil, fmt.Errorf("building inventory URL: %w", err)
+ }
+
+ inventoryContent, err := fetchURL(ctx, u.client, inventoryURL)
+ if err != nil {
+ return nil, fmt.Errorf("fetching inventory JSON %q: %w", inventoryURL, err)
+ }
+
+ hts, hostToFallbackIPs, err := parseInventoryJSON(inventoryContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing inventory JSON from %q: %w", inventoryURL, err)
+ }
+
+ localDataContent, err := extractFileFromAsar(asarContent, localDataAsarPath)
+ if err != nil {
+ u.warner.Warn(fmt.Sprintf("extracting app-bundled local data from app.asar: %v", err))
+ } else {
+ localHTS, parseErr := parseLocalData(localDataContent)
+ if parseErr != nil {
+ u.warner.Warn(fmt.Sprintf("parsing app-bundled local data: %v", parseErr))
+ } else {
+ mergeHostToServer(hts, localHTS)
}
- hts.add(host, tcp, udp)
+ localFallbackIPs := parseLocalDataFallbackIPs(localDataContent)
+ hostToFallbackIPs = mergeHostToFallbackIPs(hostToFallbackIPs, localFallbackIPs)
}
if len(hts) < minServers {
@@ -64,56 +91,196 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
hosts := hts.toHostsSlice()
resolveSettings := parallelResolverSettings(hosts)
- hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
- for _, warning := range warnings {
- u.warner.Warn(warning)
- }
+ hostToIPs, warnings, err := resolveWithMultipleResolvers(ctx, u.parallelResolver, resolveSettings)
+ warnAll(u.warner, warnings)
if err != nil {
return nil, err
}
+ applyFallbackIPs(hostToIPs, hostToFallbackIPs, hosts)
+
if len(hostToIPs) < minServers {
return nil, fmt.Errorf("%w: %d and expected at least %d",
- common.ErrNotEnoughServers, len(servers), minServers)
+ common.ErrNotEnoughServers, len(hostToIPs), minServers)
}
hts.adaptWithIPs(hostToIPs)
servers = hts.toServersSlice()
- // Get public IP address information
- ipsToGetInfo := make([]netip.Addr, len(servers))
for i := range servers {
- ipsToGetInfo[i] = servers[i].IPs[0]
+ country, city, warnings := parseHostname(servers[i].Hostname)
+ for _, warning := range warnings {
+ u.warner.Warn(warning)
+ }
+ servers[i].Country = country
+ servers[i].City = city
}
- ipsInfo, err := api.FetchMultiInfo(ctx, u.ipFetcher, ipsToGetInfo)
- if err != nil {
- return nil, err
+
+ enrichLocationBlanks(ctx, u.ipFetcher, u.warner, servers)
+
+ sort.Sort(models.SortableServers(servers))
+
+ return servers, nil
+}
+
+func enrichLocationBlanks(ctx context.Context, ipFetcher common.IPFetcher, warner common.Warner, servers []models.Server) {
+ if ipFetcher == nil || !ipFetcher.CanFetchAnyIP() {
+ return
}
for i := range servers {
- parsedCountry, parsedCity, warnings := parseHostname(servers[i].Hostname)
- for _, warning := range warnings {
- u.warner.Warn(warning)
+ if !needsGeolocationEnrichment(servers[i]) || len(servers[i].IPs) == 0 {
+ continue
+ }
+
+ result, err := ipFetcher.FetchInfo(ctx, servers[i].IPs[0])
+ if err != nil {
+ warner.Warn(fmt.Sprintf("fetching geolocation for %s (%s): %v",
+ servers[i].Hostname, servers[i].IPs[0], err))
+ continue
}
- servers[i].Country = parsedCountry
+
+ if !canApplyGeolocationCountry(servers[i].Country, result.Country) {
+ warner.Warn(fmt.Sprintf("discarding geolocation for %s (%s): country mismatch %q vs %q",
+ servers[i].Hostname, servers[i].IPs[0], servers[i].Country, result.Country))
+ continue
+ }
+
if servers[i].Country == "" {
- servers[i].Country = ipsInfo[i].Country
+ servers[i].Country = strings.TrimSpace(result.Country)
+ }
+ if servers[i].Region == "" {
+ servers[i].Region = strings.TrimSpace(result.Region)
}
- servers[i].City = parsedCity
if servers[i].City == "" {
- servers[i].City = ipsInfo[i].City
+ servers[i].City = strings.TrimSpace(result.City)
}
+ }
+}
+
+func needsGeolocationEnrichment(server models.Server) bool {
+ if strings.TrimSpace(server.Country) == "" {
+ return true
+ }
+ if strings.TrimSpace(server.City) != "" {
+ return false
+ }
+ return hostnameHasCityCode(server.Hostname)
+}
+
+func hostnameHasCityCode(hostname string) bool {
+ twoMinusIndex := strings.Index(hostname, "2-")
+ return twoMinusIndex > 2
+}
- if (parsedCountry == "" ||
- comparePlaceNames(parsedCountry, ipsInfo[i].Country)) &&
- (parsedCity == "" ||
- comparePlaceNames(parsedCity, ipsInfo[i].City)) {
- servers[i].Region = ipsInfo[i].Region
+func canApplyGeolocationCountry(inventoryCountry, geolocationCountry string) bool {
+ inventoryCountry = strings.TrimSpace(inventoryCountry)
+ geolocationCountry = strings.TrimSpace(geolocationCountry)
+ if inventoryCountry == "" || geolocationCountry == "" {
+ return true
+ }
+ return strings.EqualFold(inventoryCountry, geolocationCountry)
+}
+
+func mergeHostToServer(base, overlay hostToServer) {
+ for host, server := range overlay {
+ if server.TCP {
+ if len(server.TCPPorts) == 0 {
+ base.add(host, true, false, 0, false)
+ } else {
+ for _, port := range server.TCPPorts {
+ base.add(host, true, false, port, false)
+ }
+ }
+ }
+ if server.UDP {
+ if len(server.UDPPorts) == 0 {
+ base.add(host, false, true, 0, false)
+ } else {
+ for _, port := range server.UDPPorts {
+ base.add(host, false, true, port, false)
+ }
+ }
}
}
+}
- sort.Sort(models.SortableServers(servers))
+func mergeHostToFallbackIPs(base, overlay map[string][]netip.Addr) map[string][]netip.Addr {
+ if len(overlay) == 0 {
+ return base
+ }
+ if base == nil {
+ base = make(map[string][]netip.Addr)
+ }
+ for host, ips := range overlay {
+ for _, ip := range ips {
+ base[host] = appendIPIfMissing(base[host], ip)
+ }
+ }
+ return base
+}
- return servers, nil
+func resolveWithMultipleResolvers(ctx context.Context, primary common.ParallelResolver,
+ settings resolver.ParallelSettings,
+) (hostToIPs map[string][]netip.Addr, warnings []string, err error) {
+ hostToIPs = make(map[string][]netip.Addr, len(settings.Hosts))
+
+ mergeResult := func(newHostToIPs map[string][]netip.Addr) {
+ for host, ips := range newHostToIPs {
+ existing := hostToIPs[host]
+ for _, ip := range ips {
+ existing = appendIPIfMissing(existing, ip)
+ }
+ hostToIPs[host] = existing
+ }
+ }
+
+ primaryHostToIPs, primaryWarnings, primaryErr := primary.Resolve(ctx, settings)
+ warnings = append(warnings, primaryWarnings...)
+ if primaryErr == nil {
+ mergeResult(primaryHostToIPs)
+ } else {
+ warnings = append(warnings, primaryErr.Error())
+ }
+
+ // Try multiple DNS resolvers to recover hosts that are flaky or resolver-specific.
+ for _, dnsAddress := range []string{"1.1.1.1", "8.8.8.8", "9.9.9.9"} {
+ parallelResolver := resolver.NewParallelResolver(dnsAddress)
+ hostToIPsCandidate, candidateWarnings, candidateErr := parallelResolver.Resolve(ctx, settings)
+ warnings = append(warnings, candidateWarnings...)
+ if candidateErr != nil {
+ warnings = append(warnings, candidateErr.Error())
+ continue
+ }
+ mergeResult(hostToIPsCandidate)
+ }
+
+ if len(hostToIPs) == 0 {
+ return nil, warnings, fmt.Errorf("%w", common.ErrNotEnoughServers)
+ }
+
+ return hostToIPs, warnings, nil
+}
+
+func applyFallbackIPs(hostToIPs map[string][]netip.Addr, hostToFallbackIPs map[string][]netip.Addr, hosts []string) {
+ if len(hostToFallbackIPs) == 0 {
+ return
+ }
+ for _, host := range hosts {
+ if len(hostToIPs[host]) > 0 {
+ continue
+ }
+ fallbackIPs := hostToFallbackIPs[host]
+ if len(fallbackIPs) == 0 {
+ continue
+ }
+ hostToIPs[host] = append([]netip.Addr(nil), fallbackIPs...)
+ }
+}
+
+func warnAll(warner common.Warner, warnings []string) {
+ for _, warning := range warnings {
+ warner.Warn(warning)
+ }
}
diff --git a/internal/provider/purevpn/updater/servers_test.go b/internal/provider/purevpn/updater/servers_test.go
new file mode 100644
index 000000000..4c545ce26
--- /dev/null
+++ b/internal/provider/purevpn/updater/servers_test.go
@@ -0,0 +1,160 @@
+package updater
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/qdm12/gluetun/internal/models"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_needsGeolocationEnrichment(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ server models.Server
+ need bool
+ }{
+ "country and city present": {
+ server: models.Server{
+ Country: "United States",
+ Hostname: "usnj2-auto-udp.ptoserver.com",
+ City: "Newark",
+ },
+ need: false,
+ },
+ "missing country": {
+ server: models.Server{Hostname: "us2-auto-udp.ptoserver.com", City: "Newark"},
+ need: true,
+ },
+ "missing city but hostname has no city code": {
+ server: models.Server{Country: "United States", Hostname: "us2-auto-udp.ptoserver.com"},
+ need: false,
+ },
+ "missing city with city code in hostname": {
+ server: models.Server{Country: "United States", Hostname: "usnj2-auto-udp.ptoserver.com"},
+ need: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ need := needsGeolocationEnrichment(testCase.server)
+ assert.Equal(t, testCase.need, need)
+ })
+ }
+}
+
+func Test_hostnameHasCityCode(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ hostname string
+ hasCode bool
+ }{
+ "with city code": {
+ hostname: "usnj2-auto-udp.ptoserver.com",
+ hasCode: true,
+ },
+ "without city code": {
+ hostname: "us2-auto-udp.ptoserver.com",
+ hasCode: false,
+ },
+ "missing marker": {
+ hostname: "invalid-hostname",
+ hasCode: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ hasCode := hostnameHasCityCode(testCase.hostname)
+ assert.Equal(t, testCase.hasCode, hasCode)
+ })
+ }
+}
+
+func Test_canApplyGeolocationCountry(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ inventoryCountry string
+ geolocationCountry string
+ ok bool
+ }{
+ "empty inventory country": {
+ inventoryCountry: "",
+ geolocationCountry: "Germany",
+ ok: true,
+ },
+ "empty geolocation country": {
+ inventoryCountry: "Germany",
+ geolocationCountry: "",
+ ok: true,
+ },
+ "matching countries": {
+ inventoryCountry: "India",
+ geolocationCountry: "India",
+ ok: true,
+ },
+ "matching countries case insensitive": {
+ inventoryCountry: "United States",
+ geolocationCountry: "united states",
+ ok: true,
+ },
+ "mismatching countries": {
+ inventoryCountry: "Russia",
+ geolocationCountry: "Germany",
+ ok: false,
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ ok := canApplyGeolocationCountry(testCase.inventoryCountry, testCase.geolocationCountry)
+ assert.Equal(t, testCase.ok, ok)
+ })
+ }
+}
+
+func Test_mergeHostToServer(t *testing.T) {
+ t.Parallel()
+
+ base := make(hostToServer)
+ base.add("us2-auto-udp.ptoserver.com", false, true, 15021, false)
+
+ overlay := make(hostToServer)
+ overlay.add("usnj2-auto-tcp.ptoserver.com", true, false, 80, false)
+ overlay.add("us2-auto-udp.ptoserver.com", false, true, 1210, false)
+
+ mergeHostToServer(base, overlay)
+
+ assert.Contains(t, base, "usnj2-auto-tcp.ptoserver.com")
+ assert.Contains(t, base["usnj2-auto-tcp.ptoserver.com"].TCPPorts, uint16(80))
+ assert.ElementsMatch(t, []uint16{15021, 1210}, base["us2-auto-udp.ptoserver.com"].UDPPorts)
+}
+
+func Test_mergeHostToFallbackIPs(t *testing.T) {
+ t.Parallel()
+
+ base := map[string][]netip.Addr{
+ "us2-auto-udp.ptoserver.com": {netip.MustParseAddr("1.1.1.1")},
+ }
+ overlay := map[string][]netip.Addr{
+ "us2-auto-udp.ptoserver.com": {netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")},
+ "usnj2-auto-tcp.ptoserver.com": {netip.MustParseAddr("3.3.3.3")},
+ }
+
+ merged := mergeHostToFallbackIPs(base, overlay)
+
+ assert.Equal(t, []netip.Addr{
+ netip.MustParseAddr("1.1.1.1"),
+ netip.MustParseAddr("2.2.2.2"),
+ }, merged["us2-auto-udp.ptoserver.com"])
+ assert.Equal(t, []netip.Addr{
+ netip.MustParseAddr("3.3.3.3"),
+ }, merged["usnj2-auto-tcp.ptoserver.com"])
+}
diff --git a/internal/provider/purevpn/updater/templates.go b/internal/provider/purevpn/updater/templates.go
new file mode 100644
index 000000000..853808ca2
--- /dev/null
+++ b/internal/provider/purevpn/updater/templates.go
@@ -0,0 +1,434 @@
+package updater
+
+import (
+ "bytes"
+ "context"
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "regexp"
+ "slices"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+const (
+ atomAuthPath = "auth/v1/accessToken"
+ speedtestWithoutPSKAPIPath = "speedtest/v4/serversWithoutPsk"
+)
+
+var (
+ atomAPIBaseURLRegex = regexp.MustCompile(`AtomApi,\s*"BASE_URL",\s*"([^"]+)"`)
+ atomSecretRegex = regexp.MustCompile(`ATOM_SECRET["']?\s*[:=]\s*["']([A-Za-z0-9_-]{12,})["']`)
+ cryptoKeyRegex = regexp.MustCompile(`\bp\s*=\s*"([^"]+)"`)
+ controlCharRegex = regexp.MustCompile(`[[:cntrl:]]`)
+ configFieldNeedle = []byte(`"configuration":"`)
+ defaultAtomSecret = "MkvGuMCi6nabLqnjATh3HxN1Hh3iZI"
+)
+
+type openVPNTemplate struct {
+ Version string `json:"version"`
+ Configuration string `json:"configuration"`
+}
+
+func fetchOpenVPNTemplates(ctx context.Context, httpClient *http.Client,
+ asarContent, inventoryContent []byte, username, password string,
+) (templates []openVPNTemplate, err error) {
+ atomSecret := resolveAtomSecret(asarContent)
+
+ endpointsContent, _, err := extractFirstFileFromAsar(asarContent,
+ inventoryEndpointsAsarPath,
+ "node_modules/atom-sdk/node_modules/inventory/node_modules/utils/lib/constants/end-points.js")
+ if err != nil {
+ return nil, fmt.Errorf("extracting endpoints JS from app.asar: %w", err)
+ }
+ atomAPIBaseURL, err := parseAtomAPIBaseURL(endpointsContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing atom API base URL: %w", err)
+ }
+
+ cryptoContent, _, err := extractFirstFileFromAsar(asarContent,
+ "node_modules/atom-sdk/node_modules/utils/src/crypto.js",
+ "node_modules/atom-sdk/node_modules/utils/lib/crypto.js")
+ if err != nil {
+ return nil, fmt.Errorf("extracting crypto JS from app.asar: %w", err)
+ }
+ cryptoKeyBase64, err := parseCryptoKeyBase64(cryptoContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing crypto key from app.asar: %w", err)
+ }
+
+ inventoryVersions, err := parseInventoryConfigurationVersions(inventoryContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing configuration versions from inventory: %w", err)
+ }
+ versionSet := make(map[string]struct{}, len(inventoryVersions))
+ for _, version := range inventoryVersions {
+ versionSet[version] = struct{}{}
+ }
+
+ hts, _, err := parseInventoryJSON(inventoryContent)
+ if err != nil {
+ return nil, fmt.Errorf("parsing inventory hosts: %w", err)
+ }
+ countrySlugs := countrySlugsFromHosts(hts)
+ if len(countrySlugs) == 0 {
+ return nil, fmt.Errorf("no country slugs found in inventory hosts")
+ }
+
+ accessToken, resellerID, err := fetchAccessToken(ctx, httpClient, atomAPIBaseURL, atomSecret)
+ if err != nil {
+ return nil, fmt.Errorf("fetching atom API access token: %w", err)
+ }
+
+ encryptedPassword, err := encryptForAtom(password, cryptoKeyBase64)
+ if err != nil {
+ return nil, fmt.Errorf("encrypting password: %w", err)
+ }
+
+ templatesByVersion := make(map[string]string, len(versionSet))
+ for _, countrySlug := range countrySlugs {
+ responseBody, err := fetchSpeedtestServersWithoutPSK(ctx, httpClient, atomAPIBaseURL, accessToken,
+ resellerID, username, encryptedPassword, countrySlug)
+ if err != nil {
+ continue
+ }
+
+ servers, err := parseSpeedtestServers(responseBody)
+ if err != nil {
+ continue
+ }
+ for _, server := range servers {
+ version := strings.TrimSpace(server.ConfigurationVersion)
+ configuration := strings.TrimSpace(server.Configuration)
+ if version == "" || configuration == "" {
+ continue
+ }
+ if len(versionSet) > 0 {
+ if _, needed := versionSet[version]; !needed {
+ continue
+ }
+ }
+ if _, exists := templatesByVersion[version]; exists {
+ continue
+ }
+ templatesByVersion[version] = configuration
+ }
+
+ if len(versionSet) > 0 && len(templatesByVersion) == len(versionSet) {
+ break
+ }
+ }
+
+ versions := make([]string, 0, len(templatesByVersion))
+ for version := range templatesByVersion {
+ versions = append(versions, version)
+ }
+ sort.Strings(versions)
+
+ templates = make([]openVPNTemplate, 0, len(versions))
+ for _, version := range versions {
+ templates = append(templates, openVPNTemplate{
+ Version: version,
+ Configuration: templatesByVersion[version],
+ })
+ }
+
+ return templates, nil
+}
+
+func resolveAtomSecret(asarContent []byte) (atomSecret string) {
+ if extracted := parseAtomSecretFromContent(asarContent); extracted != "" {
+ return extracted
+ }
+
+ return defaultAtomSecret
+}
+
+func parseAtomSecretFromContent(content []byte) (atomSecret string) {
+ match := atomSecretRegex.FindSubmatch(content)
+ if len(match) != 2 {
+ return ""
+ }
+ return strings.TrimSpace(string(match[1]))
+}
+
+func parseAtomAPIBaseURL(content []byte) (baseURL string, err error) {
+ match := atomAPIBaseURLRegex.FindSubmatch(content)
+ if len(match) != 2 {
+ return "", fmt.Errorf("atom API base URL not found")
+ }
+ baseURL = strings.TrimSpace(string(match[1]))
+ if baseURL == "" {
+ return "", fmt.Errorf("atom API base URL is empty")
+ }
+ if !strings.HasSuffix(baseURL, "/") {
+ baseURL += "/"
+ }
+ return baseURL, nil
+}
+
+func parseCryptoKeyBase64(content []byte) (keyBase64 string, err error) {
+ match := cryptoKeyRegex.FindSubmatch(content)
+ if len(match) != 2 {
+ return "", fmt.Errorf("crypto key not found")
+ }
+ keyBase64 = strings.TrimSpace(string(match[1]))
+ if keyBase64 == "" {
+ return "", fmt.Errorf("crypto key is empty")
+ }
+ return keyBase64, nil
+}
+
+func fetchAccessToken(ctx context.Context, httpClient *http.Client,
+ baseURL, atomSecret string,
+) (accessToken, resellerID string, err error) {
+ payload := map[string]string{
+ "secretKey": atomSecret,
+ "grantType": "secret",
+ }
+
+ data, err := json.Marshal(payload)
+ if err != nil {
+ return "", "", fmt.Errorf("marshalling auth request: %w", err)
+ }
+
+ request, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+atomAuthPath, bytes.NewReader(data))
+ if err != nil {
+ return "", "", fmt.Errorf("creating auth request: %w", err)
+ }
+ request.Header.Set("Content-Type", "application/json")
+
+ response, err := httpClient.Do(request)
+ if err != nil {
+ return "", "", fmt.Errorf("doing auth request: %w", err)
+ }
+ defer response.Body.Close()
+
+ responseData := map[string]any{}
+ if err := json.NewDecoder(response.Body).Decode(&responseData); err != nil {
+ return "", "", fmt.Errorf("decoding auth response: %w", err)
+ }
+
+ bodyMap, _ := responseData["body"].(map[string]any)
+ accessToken = strings.TrimSpace(fmt.Sprint(bodyMap["accessToken"]))
+ resellerID = strings.TrimSpace(fmt.Sprint(bodyMap["resellerId"]))
+ if accessToken == "" || resellerID == "" {
+ return "", "", fmt.Errorf("access token or reseller id missing in auth response")
+ }
+
+ return accessToken, resellerID, nil
+}
+
+func encryptForAtom(plainText, keyBase64 string) (encrypted string, err error) {
+ key, err := base64.StdEncoding.DecodeString(keyBase64)
+ if err != nil {
+ return "", fmt.Errorf("decoding base64 key: %w", err)
+ }
+ if len(key) != 16 && len(key) != 32 {
+ return "", fmt.Errorf("invalid key length: %d", len(key))
+ }
+
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return "", fmt.Errorf("creating AES cipher: %w", err)
+ }
+ iv := key[:aes.BlockSize]
+ plainBytes := []byte(plainText)
+ padded := pkcs7Pad(plainBytes, aes.BlockSize)
+
+ cipherText := make([]byte, len(padded))
+ mode := cipher.NewCBCEncrypter(block, iv)
+ mode.CryptBlocks(cipherText, padded)
+
+ return base64.StdEncoding.EncodeToString(cipherText), nil
+}
+
+func pkcs7Pad(data []byte, blockSize int) []byte {
+ padding := blockSize - len(data)%blockSize
+ if padding == 0 {
+ padding = blockSize
+ }
+ padded := make([]byte, len(data)+padding)
+ copy(padded, data)
+ for i := len(data); i < len(padded); i++ {
+ padded[i] = byte(padding)
+ }
+ return padded
+}
+
+type speedtestServer struct {
+ ConfigurationVersion string `json:"configuration_version"`
+ Configuration string `json:"configuration"`
+}
+
+func fetchSpeedtestServersWithoutPSK(ctx context.Context, httpClient *http.Client,
+ baseURL, accessToken, resellerID, username, encryptedPassword, countrySlug string,
+) (responseBody []byte, err error) {
+ payload := map[string]any{
+ "sCountrySlug": countrySlug,
+ "iMultiPort": 0,
+ "sProtocolSlug1": "udp",
+ "sProtocolSlug2": "tcp",
+ "sProtocolSlug3": "",
+ "iMcs": 1,
+ "iResellerId": resellerID,
+ "sDeviceType": "linux",
+ "sUsername": username,
+ "sPassword": encryptedPassword,
+ "aServerFilter": []string{},
+ "iNatting": 0,
+ }
+
+ data, err := json.Marshal(payload)
+ if err != nil {
+ return nil, fmt.Errorf("marshalling speedtest request: %w", err)
+ }
+
+ request, err := http.NewRequestWithContext(ctx, http.MethodPost,
+ baseURL+speedtestWithoutPSKAPIPath, bytes.NewReader(data))
+ if err != nil {
+ return nil, fmt.Errorf("creating speedtest request: %w", err)
+ }
+ request.Header.Set("Content-Type", "application/json")
+ request.Header.Set("X-AccessToken", accessToken)
+
+ response, err := httpClient.Do(request)
+ if err != nil {
+ return nil, fmt.Errorf("doing speedtest request: %w", err)
+ }
+ defer response.Body.Close()
+
+ return ioReadAllAndSanitize(response)
+}
+
+func ioReadAllAndSanitize(response *http.Response) ([]byte, error) {
+ var responseBody bytes.Buffer
+ if _, err := responseBody.ReadFrom(response.Body); err != nil {
+ return nil, fmt.Errorf("reading speedtest response body: %w", err)
+ }
+ return sanitizeSpeedtestResponseJSON(responseBody.Bytes()), nil
+}
+
+func sanitizeSpeedtestResponseJSON(content []byte) []byte {
+ if json.Valid(content) {
+ return content
+ }
+
+ result := make([]byte, 0, len(content)+1024)
+ for index := 0; index < len(content); {
+ relative := bytes.Index(content[index:], configFieldNeedle)
+ if relative == -1 {
+ result = append(result, content[index:]...)
+ break
+ }
+ start := index + relative
+ result = append(result, content[index:start]...)
+ result = append(result, configFieldNeedle...)
+ valueStart := start + len(configFieldNeedle)
+ valueEnd := valueStart
+ for valueEnd < len(content) {
+ if content[valueEnd] == '"' && (valueEnd == valueStart || content[valueEnd-1] != '\\') {
+ break
+ }
+ valueEnd++
+ }
+ if valueEnd >= len(content) {
+ result = append(result, content[valueStart:]...)
+ break
+ }
+ escaped := strings.Trim(strconvQuote(string(content[valueStart:valueEnd])), `"`)
+ result = append(result, escaped...)
+ result = append(result, '"')
+ index = valueEnd + 1
+ }
+ return result
+}
+
+func strconvQuote(value string) string {
+ value = controlCharRegex.ReplaceAllString(value, "")
+ return strconv.Quote(value)
+}
+
+func parseSpeedtestServers(content []byte) (servers []speedtestServer, err error) {
+ var response struct {
+ Body any `json:"body"`
+ }
+ if err := json.Unmarshal(content, &response); err != nil {
+ return nil, fmt.Errorf("unmarshalling speedtest response: %w", err)
+ }
+
+ bodyBytes, err := json.Marshal(response.Body)
+ if err != nil {
+ return nil, fmt.Errorf("marshalling speedtest body: %w", err)
+ }
+
+ if err := json.Unmarshal(bodyBytes, &servers); err == nil && len(servers) > 0 {
+ return servers, nil
+ }
+
+ var bodyObject struct {
+ Servers []speedtestServer `json:"servers"`
+ }
+ if err := json.Unmarshal(bodyBytes, &bodyObject); err == nil {
+ return bodyObject.Servers, nil
+ }
+
+ return nil, nil
+}
+
+func countrySlugsFromHosts(hts hostToServer) (countrySlugs []string) {
+ set := make(map[string]struct{})
+ for host := range hts {
+ countrySlug := parsePureVPNCountrySlug(host)
+ if countrySlug == "" {
+ continue
+ }
+ if _, ok := set[countrySlug]; ok {
+ continue
+ }
+ set[countrySlug] = struct{}{}
+ countrySlugs = append(countrySlugs, countrySlug)
+ }
+
+ sort.Strings(countrySlugs)
+
+ // Pulling from the largest geographies first tends to recover all active
+ // configuration versions with fewer requests.
+ prioritized := []string{"us", "uk", "de", "fr", "nl", "sg", "jp", "au", "ca"}
+ sort.SliceStable(countrySlugs, func(i, j int) bool {
+ iIndex := slices.Index(prioritized, countrySlugs[i])
+ jIndex := slices.Index(prioritized, countrySlugs[j])
+ if iIndex == -1 {
+ iIndex = len(prioritized) + i
+ }
+ if jIndex == -1 {
+ jIndex = len(prioritized) + j
+ }
+ return iIndex < jIndex
+ })
+
+ return countrySlugs
+}
+
+func parsePureVPNCountrySlug(hostname string) (countrySlug string) {
+ firstLabel := hostname
+ if dotIndex := strings.IndexByte(hostname, '.'); dotIndex > -1 {
+ firstLabel = hostname[:dotIndex]
+ }
+
+ twoMinusIndex := strings.Index(firstLabel, "2-")
+ if twoMinusIndex <= 0 {
+ return ""
+ }
+
+ locationCode := strings.ToLower(firstLabel[:twoMinusIndex])
+ if len(locationCode) < 2 {
+ return ""
+ }
+ return locationCode[:2]
+}
diff --git a/internal/provider/purevpn/updater/templates_test.go b/internal/provider/purevpn/updater/templates_test.go
new file mode 100644
index 000000000..2b620bf68
--- /dev/null
+++ b/internal/provider/purevpn/updater/templates_test.go
@@ -0,0 +1,34 @@
+package updater
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_parseAtomAPIBaseURL(t *testing.T) {
+ t.Parallel()
+
+ content := []byte(`(0, _defineProperty2["default"])(AtomApi, "BASE_URL", "https://atomapi.com/");`)
+ baseURL, err := parseAtomAPIBaseURL(content)
+ require.NoError(t, err)
+ assert.Equal(t, "https://atomapi.com/", baseURL)
+}
+
+func Test_parsePureVPNCountrySlug(t *testing.T) {
+ t.Parallel()
+
+ assert.Equal(t, "us", parsePureVPNCountrySlug("usca2-auto-udp.ptoserver.com"))
+ assert.Equal(t, "uk", parsePureVPNCountrySlug("uk2-auto-udp.ptoserver.com"))
+ assert.Equal(t, "", parsePureVPNCountrySlug("broken-hostname"))
+}
+
+func Test_resolveAtomSecret(t *testing.T) {
+ t.Parallel()
+
+ extracted := resolveAtomSecret([]byte(`ATOM_SECRET:"fromasar123456"`))
+ assert.Equal(t, "fromasar123456", extracted)
+ fallback := resolveAtomSecret(nil)
+ assert.Equal(t, defaultAtomSecret, fallback)
+}
diff --git a/internal/provider/purevpn/updater/traits.go b/internal/provider/purevpn/updater/traits.go
new file mode 100644
index 000000000..b8781dd54
--- /dev/null
+++ b/internal/provider/purevpn/updater/traits.go
@@ -0,0 +1,25 @@
+package updater
+
+import "strings"
+
+func inferPureVPNTraits(hostname string) (portForward, quantumResistant, obfuscated, p2p bool) {
+ labels := strings.Split(strings.ToLower(hostname), ".")
+ if len(labels) == 0 {
+ return false, false, false, false
+ }
+
+ for _, token := range strings.Split(labels[0], "-") {
+ switch token {
+ case "pf":
+ portForward = true
+ case "qr":
+ quantumResistant = true
+ case "obf":
+ obfuscated = true
+ case "p2p":
+ p2p = true
+ }
+ }
+
+ return portForward, quantumResistant, obfuscated, p2p
+}
diff --git a/internal/provider/purevpn/updater/traits_test.go b/internal/provider/purevpn/updater/traits_test.go
new file mode 100644
index 000000000..e77fdd669
--- /dev/null
+++ b/internal/provider/purevpn/updater/traits_test.go
@@ -0,0 +1,55 @@
+package updater
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_inferPureVPNTraits(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ hostname string
+ portForward, qr, obfuscated bool
+ p2p bool
+ }{
+ "regular": {
+ hostname: "us2-udp.ptoserver.com",
+ },
+ "port forwarding": {
+ hostname: "us2-udp-pf.ptoserver.com",
+ portForward: true,
+ },
+ "quantum resistant": {
+ hostname: "us2-auto-udp-qr.ptoserver.com",
+ qr: true,
+ },
+ "obfuscated": {
+ hostname: "us2-obf-udp.ptoserver.com",
+ obfuscated: true,
+ },
+ "multiple traits": {
+ hostname: "us2-udp-qr-pf.ptoserver.com",
+ portForward: true,
+ qr: true,
+ },
+ "p2p": {
+ hostname: "fi-p2p.jumptoserver.com",
+ p2p: true,
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ portForward, qr, obfuscated, p2p := inferPureVPNTraits(testCase.hostname)
+
+ assert.Equal(t, testCase.portForward, portForward)
+ assert.Equal(t, testCase.qr, qr)
+ assert.Equal(t, testCase.obfuscated, obfuscated)
+ assert.Equal(t, testCase.p2p, p2p)
+ })
+ }
+}
diff --git a/internal/provider/purevpn/updater/updater.go b/internal/provider/purevpn/updater/updater.go
index 2136f8ee6..c21b71949 100644
--- a/internal/provider/purevpn/updater/updater.go
+++ b/internal/provider/purevpn/updater/updater.go
@@ -1,20 +1,28 @@
package updater
import (
+ "net/http"
+
"github.com/qdm12/gluetun/internal/provider/common"
)
type Updater struct {
+ client *http.Client
ipFetcher common.IPFetcher
unzipper common.Unzipper
parallelResolver common.ParallelResolver
warner common.Warner
}
-func New(ipFetcher common.IPFetcher, unzipper common.Unzipper,
+func New(client *http.Client, ipFetcher common.IPFetcher, unzipper common.Unzipper,
warner common.Warner, parallelResolver common.ParallelResolver,
) *Updater {
+ if client == nil {
+ client = http.DefaultClient
+ }
+
return &Updater{
+ client: client,
ipFetcher: ipFetcher,
unzipper: unzipper,
parallelResolver: parallelResolver,
diff --git a/internal/provider/utils/connection.go b/internal/provider/utils/connection.go
index 7e10d7397..3b860feec 100644
--- a/internal/provider/utils/connection.go
+++ b/internal/provider/utils/connection.go
@@ -5,6 +5,7 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/configuration/settings"
+ "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
)
@@ -61,10 +62,18 @@ func GetConnection(provider string,
hostname = server.OvpnX509
}
+ portForServer := port
+ customOpenVPNPortSet := selection.OpenVPN.CustomPort != nil &&
+ *selection.OpenVPN.CustomPort != 0
+ if !customOpenVPNPortSet && selection.VPN == vpn.OpenVPN {
+ portForServer = getPortForServer(server, protocol,
+ defaults.OpenVPNTCPPort, defaults.OpenVPNUDPPort)
+ }
+
connection := models.Connection{
Type: selection.VPN,
IP: ip,
- Port: port,
+ Port: portForServer,
Protocol: protocol,
Hostname: hostname,
ServerName: server.ServerName,
@@ -77,3 +86,29 @@ func GetConnection(provider string,
return pickConnection(connections, selection, randSource)
}
+
+func getPortForServer(server models.Server, protocol string, defaultTCPPort, defaultUDPPort uint16) (port uint16) {
+ switch protocol {
+ case constants.TCP:
+ ports := make([]uint16, 0, len(server.TCPPorts)+3)
+ ports = append(ports, server.TCPPorts...)
+ ports = append(ports, defaultTCPPort, 443, 1194)
+ return firstNonZeroPort(ports)
+ case constants.UDP:
+ ports := make([]uint16, 0, len(server.UDPPorts)+3)
+ ports = append(ports, server.UDPPorts...)
+ ports = append(ports, defaultUDPPort, 1194, 53)
+ return firstNonZeroPort(ports)
+ default:
+ return 0
+ }
+}
+
+func firstNonZeroPort(ports []uint16) (port uint16) {
+ for _, port := range ports {
+ if port != 0 {
+ return port
+ }
+ }
+ return 0
+}
diff --git a/internal/provider/utils/connection_test.go b/internal/provider/utils/connection_test.go
index 37fb66f2b..6ef0bc84a 100644
--- a/internal/provider/utils/connection_test.go
+++ b/internal/provider/utils/connection_test.go
@@ -58,6 +58,7 @@ func Test_GetConnection(t *testing.T) {
{
VPN: vpn.OpenVPN,
UDP: true,
+ UDPPorts: []uint16{15021},
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})},
Hostname: "name",
},
@@ -70,7 +71,7 @@ func Test_GetConnection(t *testing.T) {
Type: vpn.OpenVPN,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Protocol: constants.UDP,
- Port: 1194,
+ Port: 15021,
Hostname: "name",
},
},
@@ -79,6 +80,7 @@ func Test_GetConnection(t *testing.T) {
{
VPN: vpn.OpenVPN,
UDP: true,
+ UDPPorts: []uint16{15021},
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})},
Hostname: "hostname",
OvpnX509: "x509",
@@ -92,15 +94,90 @@ func Test_GetConnection(t *testing.T) {
Type: vpn.OpenVPN,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Protocol: constants.UDP,
- Port: 1194,
+ Port: 15021,
Hostname: "x509",
},
},
+ "OpenVPN server uses protocol-specific TCP port when no custom port set": {
+ filteredServers: []models.Server{
+ {
+ VPN: vpn.OpenVPN,
+ TCP: true,
+ TCPPorts: []uint16{4433},
+ IPs: []netip.Addr{netip.AddrFrom4([4]byte{8, 8, 8, 8})},
+ Hostname: "name",
+ },
+ },
+ serverSelection: func() settings.ServerSelection {
+ ss := settings.ServerSelection{}.WithDefaults(providers.Mullvad)
+ ss.OpenVPN.Protocol = constants.TCP
+ return ss
+ }(),
+ defaults: NewConnectionDefaults(443, 1194, 58820),
+ randSource: rand.NewSource(0),
+ connection: models.Connection{
+ Type: vpn.OpenVPN,
+ IP: netip.AddrFrom4([4]byte{8, 8, 8, 8}),
+ Protocol: constants.TCP,
+ Port: 4433,
+ Hostname: "name",
+ },
+ },
+ "OpenVPN server uses protocol-specific UDP port when no custom port set": {
+ filteredServers: []models.Server{
+ {
+ VPN: vpn.OpenVPN,
+ UDP: true,
+ UDPPorts: []uint16{15021},
+ IPs: []netip.Addr{netip.AddrFrom4([4]byte{9, 9, 9, 9})},
+ Hostname: "name",
+ },
+ },
+ serverSelection: settings.ServerSelection{}.
+ WithDefaults(providers.Mullvad),
+ defaults: NewConnectionDefaults(443, 1194, 58820),
+ randSource: rand.NewSource(0),
+ connection: models.Connection{
+ Type: vpn.OpenVPN,
+ IP: netip.AddrFrom4([4]byte{9, 9, 9, 9}),
+ Protocol: constants.UDP,
+ Port: 15021,
+ Hostname: "name",
+ },
+ },
+ "OpenVPN explicit custom port overrides protocol-specific port": {
+ filteredServers: []models.Server{
+ {
+ VPN: vpn.OpenVPN,
+ UDP: true,
+ UDPPorts: []uint16{15021},
+ IPs: []netip.Addr{netip.AddrFrom4([4]byte{10, 10, 10, 10})},
+ Hostname: "name",
+ },
+ },
+ serverSelection: func() settings.ServerSelection {
+ ss := settings.ServerSelection{}.WithDefaults(providers.Mullvad)
+ *ss.OpenVPN.CustomPort = 1194
+ return ss
+ }(),
+ defaults: NewConnectionDefaults(443, 53, 58820),
+ randSource: rand.NewSource(0),
+ connection: models.Connection{
+ Type: vpn.OpenVPN,
+ IP: netip.AddrFrom4([4]byte{10, 10, 10, 10}),
+ Protocol: constants.UDP,
+ Port: 1194,
+ Hostname: "name",
+ },
+ },
"server with IPv4 and IPv6": {
filteredServers: []models.Server{
{
VPN: vpn.OpenVPN,
UDP: true,
+ UDPPorts: []uint16{
+ 15021,
+ },
IPs: []netip.Addr{
netip.AddrFrom4([4]byte{1, 1, 1, 1}),
// All IPv6 is ignored
@@ -120,7 +197,7 @@ func Test_GetConnection(t *testing.T) {
Type: vpn.OpenVPN,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Protocol: constants.UDP,
- Port: 1194,
+ Port: 15021,
},
},
"server with IPv4 and IPv6 and ipv6 supported": {
@@ -128,6 +205,9 @@ func Test_GetConnection(t *testing.T) {
{
VPN: vpn.OpenVPN,
UDP: true,
+ UDPPorts: []uint16{
+ 15021,
+ },
IPs: []netip.Addr{
netip.IPv6Unspecified(),
netip.AddrFrom4([4]byte{1, 1, 1, 1}),
@@ -143,7 +223,7 @@ func Test_GetConnection(t *testing.T) {
Type: vpn.OpenVPN,
IP: netip.IPv6Unspecified(),
Protocol: constants.UDP,
- Port: 1194,
+ Port: 15021,
},
},
"mixed servers": {
@@ -151,6 +231,7 @@ func Test_GetConnection(t *testing.T) {
{
VPN: vpn.OpenVPN,
UDP: true,
+ UDPPorts: []uint16{15021},
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})},
OvpnX509: "ovpnx509",
},
@@ -163,6 +244,7 @@ func Test_GetConnection(t *testing.T) {
{
VPN: vpn.OpenVPN,
UDP: true,
+ UDPPorts: []uint16{15021},
IPs: []netip.Addr{
netip.AddrFrom4([4]byte{3, 3, 3, 3}),
netip.AddrFrom16([16]byte{1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), // ipv6 ignored
@@ -178,7 +260,7 @@ func Test_GetConnection(t *testing.T) {
Type: vpn.OpenVPN,
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
Protocol: constants.UDP,
- Port: 1194,
+ Port: 15021,
Hostname: "ovpnx509",
},
},
@@ -206,3 +288,41 @@ func Test_GetConnection(t *testing.T) {
})
}
}
+
+func Test_getPortForServer_InventoryPorts(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ server models.Server
+ protocol string
+ defaultTCPPort uint16
+ defaultUDPPort uint16
+ expectedPort uint16
+ }{
+ "TCP uses inventory port": {
+ server: models.Server{TCPPorts: []uint16{80, 443}},
+ protocol: constants.TCP,
+ defaultTCPPort: 443,
+ defaultUDPPort: 15021,
+ expectedPort: 80,
+ },
+ "UDP uses inventory port": {
+ server: models.Server{UDPPorts: []uint16{15021, 1194}},
+ protocol: constants.UDP,
+ defaultTCPPort: 443,
+ defaultUDPPort: 15021,
+ expectedPort: 15021,
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ port := getPortForServer(testCase.server, testCase.protocol,
+ testCase.defaultTCPPort, testCase.defaultUDPPort)
+
+ assert.Equal(t, testCase.expectedPort, port)
+ })
+ }
+}
diff --git a/internal/storage/servers.json b/internal/storage/servers.json
index bc4053de5..bfff73043 100644
--- a/internal/storage/servers.json
+++ b/internal/storage/servers.json
@@ -281683,2111 +281683,8541 @@
},
"purevpn": {
"version": 3,
- "timestamp": 1702566944,
+ "timestamp": 1772170504,
"servers": [
{
"vpn": "openvpn",
- "country": "Albania",
- "region": "Tirana",
- "city": "Tirana",
- "hostname": "al2-auto-tcp.ptoserver.com",
+ "country": "Afghanistan",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "af2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "31.171.155.198",
- "31.171.155.199"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Albania",
- "region": "Tirana",
- "city": "Tirana",
- "hostname": "al2-auto-udp.ptoserver.com",
+ "country": "Afghanistan",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "af2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "31.171.155.198",
- "31.171.155.199"
+ "172.111.208.103",
+ "172.111.208.102"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "New South Wales",
- "city": "Sydney",
- "hostname": "au2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Afghanistan",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "af2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "91.242.215.105",
- "118.127.59.226",
- "223.252.60.100",
- "223.252.60.202"
+ "172.111.208.101",
+ "172.111.208.106"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "New South Wales",
- "city": "Sydney",
- "hostname": "ausd2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Afghanistan",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "af2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "27.50.76.167",
- "91.242.215.105",
- "91.242.215.106"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "New South Wales",
- "city": "Sydney",
- "hostname": "ausd2-auto-udp.ptoserver.com",
- "udp": true,
+ "country": "Afghanistan",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "af2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "27.50.76.169",
- "91.242.215.106"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "Victoria",
- "city": "Melbourne",
- "hostname": "au2-auto-udp.ptoserver.com",
+ "country": "Afghanistan",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "af2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "118.127.59.226",
- "118.127.62.3",
- "172.94.124.11",
- "223.252.60.100"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "Victoria",
- "city": "Melbourne",
- "hostname": "aume2-auto-tcp.ptoserver.com",
+ "country": "Albania",
+ "hostname": "al2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "27.50.74.4",
- "27.50.74.6",
- "118.127.59.226"
+ "79.171.52.18"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "Victoria",
- "city": "Melbourne",
- "hostname": "aume2-auto-udp.ptoserver.com",
+ "country": "Albania",
+ "hostname": "al2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "27.50.74.4",
- "118.127.59.226"
+ "31.171.153.77",
+ "31.171.154.140",
+ "31.171.153.76",
+ "31.171.154.138",
+ "31.171.154.139",
+ "31.171.153.75"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "Victoria",
- "city": "Melbourne",
- "hostname": "nz2-auto-udp.ptoserver.com",
+ "country": "Albania",
+ "hostname": "al2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "203.209.219.39",
- "203.209.219.40"
- ]
- },
- {
- "vpn": "openvpn",
- "country": "Australia",
- "region": "Western Australia",
- "city": "Perth",
- "hostname": "au2-pe-tcp.ptoserver.com",
- "tcp": true,
- "ips": [
- "43.250.205.51"
+ "31.171.153.73",
+ "31.171.154.135",
+ "31.171.154.136",
+ "31.171.154.137",
+ "31.171.153.72",
+ "31.171.153.74"
]
},
{
"vpn": "openvpn",
- "country": "Australia",
- "region": "Western Australia",
- "city": "Perth",
- "hostname": "au2-pe-udp.ptoserver.com",
+ "country": "Albania",
+ "hostname": "al2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "43.250.205.50",
- "43.250.205.53",
- "43.250.205.54",
- "43.250.205.61",
- "172.94.123.4"
+ "79.171.52.18"
]
},
{
"vpn": "openvpn",
- "country": "Austria",
- "region": "Vienna",
- "city": "Vienna",
- "hostname": "at2-auto-tcp.ptoserver.com",
+ "country": "Albania",
+ "hostname": "al2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "149.40.52.198"
+ "79.171.52.18"
]
},
{
"vpn": "openvpn",
- "country": "Austria",
- "region": "Vienna",
- "city": "Vienna",
- "hostname": "at2-auto-udp.ptoserver.com",
+ "country": "Albania",
+ "hostname": "al2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "149.40.52.197",
- "149.40.52.198"
+ "79.171.52.18"
]
},
{
"vpn": "openvpn",
- "country": "Belgium",
- "region": "Brussels Capital",
- "city": "Brussels",
- "hostname": "be2-auto-tcp.ptoserver.com",
+ "country": "Algeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dz2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "154.47.27.70"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Belgium",
- "region": "Brussels Capital",
- "city": "Brussels",
- "hostname": "be2-auto-udp.ptoserver.com",
+ "country": "Algeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dz2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "154.47.27.96"
+ "141.98.102.57",
+ "141.98.102.58",
+ "141.98.102.59"
]
},
{
"vpn": "openvpn",
- "country": "Brazil",
- "region": "São Paulo",
- "city": "São Paulo",
- "hostname": "br2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Algeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dz2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "149.102.251.6"
+ "141.98.102.54",
+ "141.98.102.55",
+ "141.98.102.56"
]
},
{
"vpn": "openvpn",
- "country": "Brazil",
- "region": "São Paulo",
- "city": "São Paulo",
- "hostname": "br2-auto-udp.ptoserver.com",
+ "country": "Algeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dz2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "149.102.251.6"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Bulgaria",
- "region": "Sofia-Capital",
- "city": "Sofia",
- "hostname": "bg2-auto-tcp.ptoserver.com",
+ "country": "Algeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dz2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "185.94.192.134",
- "185.94.192.135"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Bulgaria",
- "region": "Sofia-Capital",
- "city": "Sofia",
- "hostname": "bg2-auto-udp.ptoserver.com",
+ "country": "Algeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dz2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "185.94.192.135",
- "185.94.192.136"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Canada",
- "region": "British Columbia",
- "city": "Vancouver",
- "hostname": "ca2-auto-tcp.ptoserver.com",
+ "country": "Angola",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ao2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "104.193.135.42",
- "104.193.135.43",
- "198.144.155.70"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Canada",
- "region": "British Columbia",
- "city": "Vancouver",
- "hostname": "ca2-auto-udp.ptoserver.com",
+ "country": "Angola",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ao2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "104.193.135.9",
- "104.193.135.10",
- "104.193.135.12",
- "149.34.249.69",
- "149.34.249.78"
+ "172.94.84.167",
+ "172.94.84.168"
]
},
{
"vpn": "openvpn",
- "country": "Canada",
- "region": "British Columbia",
- "city": "Vancouver",
- "hostname": "caq2-auto-udp.ptoserver.com",
+ "country": "Angola",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ao2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "104.193.135.10",
- "104.193.135.12"
+ "172.94.84.171"
]
},
{
"vpn": "openvpn",
- "country": "Canada",
- "region": "British Columbia",
- "city": "Vancouver",
- "hostname": "cav2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Angola",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ao2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "104.193.135.9",
- "104.193.135.10",
- "104.193.135.42",
- "104.193.135.43"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Canada",
- "region": "British Columbia",
- "city": "Vancouver",
- "hostname": "cav2-auto-udp.ptoserver.com",
- "udp": true,
+ "country": "Angola",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ao2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "104.193.135.11",
- "104.193.135.12"
+ "172.94.84.132",
+ "172.94.84.134"
]
},
{
"vpn": "openvpn",
- "country": "Canada",
- "region": "Ontario",
- "city": "Toronto",
- "hostname": "caq2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Angola",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ao2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "198.144.155.69",
- "198.144.155.70"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Costa Rica",
- "region": "San José",
- "city": "San Pedro",
- "hostname": "cr2-auto-tcp.ptoserver.com",
+ "country": "Argentina",
+ "hostname": "ar2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "143.202.163.102",
- "143.202.163.103"
+ "146.70.41.34",
+ "67.73.142.22"
]
},
{
"vpn": "openvpn",
- "country": "Costa Rica",
- "region": "San José",
- "city": "San Pedro",
- "hostname": "cr2-auto-udp.ptoserver.com",
+ "country": "Argentina",
+ "hostname": "ar2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "143.202.163.103",
- "172.111.170.6"
+ "84.233.234.57",
+ "84.233.234.58",
+ "84.233.234.59",
+ "84.233.234.85",
+ "84.233.234.86",
+ "84.233.234.84"
]
},
{
"vpn": "openvpn",
- "country": "Cyprus",
- "region": "Nicosia",
- "city": "Nicosia",
- "hostname": "cy2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Argentina",
+ "hostname": "ar2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "185.191.206.199"
+ "84.233.234.54",
+ "84.233.234.82",
+ "84.233.234.56",
+ "84.233.234.55",
+ "84.233.234.83"
]
},
{
"vpn": "openvpn",
- "country": "Czech Republic",
- "region": "Prague",
- "city": "Prague",
- "hostname": "cz2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Argentina",
+ "hostname": "ar2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "87.249.135.101",
- "87.249.135.102"
+ "146.70.41.34",
+ "67.73.142.22"
]
},
{
"vpn": "openvpn",
- "country": "Czech Republic",
- "region": "Prague",
- "city": "Prague",
- "hostname": "cz2-auto-udp.ptoserver.com",
- "udp": true,
+ "country": "Argentina",
+ "hostname": "ar2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "87.249.135.102"
+ "146.70.41.34",
+ "67.73.142.22"
]
},
{
"vpn": "openvpn",
- "country": "Denmark",
- "region": "Capital Region",
- "city": "Copenhagen",
- "hostname": "dk2-auto-udp.ptoserver.com",
+ "country": "Argentina",
+ "hostname": "ar2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "149.50.217.132"
+ "146.70.41.34",
+ "67.73.142.22"
]
},
{
"vpn": "openvpn",
- "country": "Estonia",
- "region": "Harjumaa",
- "city": "Tallinn",
- "hostname": "ee2-auto-tcp.ptoserver.com",
+ "country": "Aruba",
+ "hostname": "aw2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "185.174.159.230",
- "185.174.159.247"
+ "146.70.41.34"
]
},
{
"vpn": "openvpn",
- "country": "Estonia",
- "region": "Harjumaa",
- "city": "Tallinn",
- "hostname": "ee2-auto-udp.ptoserver.com",
+ "country": "Aruba",
+ "hostname": "aw2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "185.174.159.246",
- "185.174.159.247"
+ "172.94.26.13",
+ "172.94.26.15",
+ "172.94.26.16",
+ "172.94.26.14"
]
},
{
"vpn": "openvpn",
- "country": "Finland",
- "region": "Uusimaa",
- "city": "Helsinki",
- "hostname": "fi2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Aruba",
+ "hostname": "aw2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "85.208.3.215"
+ "172.94.26.11",
+ "172.94.26.12",
+ "172.94.26.10"
]
},
{
"vpn": "openvpn",
- "country": "Finland",
- "region": "Uusimaa",
- "city": "Helsinki",
- "hostname": "fi2-auto-udp.ptoserver.com",
+ "country": "Aruba",
+ "hostname": "aw2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "85.208.3.197",
- "85.208.3.198",
- "85.208.3.215",
- "85.208.3.216"
+ "146.70.41.34"
]
},
{
"vpn": "openvpn",
- "country": "France",
- "region": "Île-de-France",
- "city": "Paris",
- "hostname": "fr2-auto-tcp.ptoserver.com",
+ "country": "Aruba",
+ "hostname": "aw2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "37.19.217.86",
- "149.34.245.196",
- "149.34.245.197"
+ "146.70.41.34"
]
},
{
"vpn": "openvpn",
- "country": "France",
- "region": "Île-de-France",
- "city": "Paris",
- "hostname": "fr2-auto-udp.ptoserver.com",
+ "country": "Aruba",
+ "hostname": "aw2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "37.19.217.86",
- "149.34.245.197"
+ "146.70.41.34"
]
},
{
"vpn": "openvpn",
- "country": "Georgia",
- "region": "T'bilisi",
- "city": "Tbilisi",
- "hostname": "ge2-auto-tcp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "au2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "45.138.44.162",
- "188.93.88.206"
+ "146.70.141.26",
+ "87.117.212.41",
+ "27.50.76.1",
+ "118.127.59.83",
+ "203.209.209.167",
+ "91.242.215.101",
+ "63.218.156.70",
+ "168.1.1.212",
+ "43.250.205.18",
+ "118.127.62.85",
+ "118.127.7.179",
+ "103.76.165.69",
+ "170.64.184.217"
]
},
{
"vpn": "openvpn",
- "country": "Georgia",
- "region": "T'bilisi",
- "city": "Tbilisi",
- "hostname": "ge2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "au2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "45.138.44.162",
- "188.93.88.206"
+ "103.107.197.170",
+ "217.79.116.202",
+ "217.79.116.203",
+ "103.107.197.172"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "af2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "hostname": "au2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "172.111.208.6"
+ "146.70.141.26",
+ "87.117.212.41",
+ "27.50.76.1",
+ "118.127.59.83",
+ "203.209.209.167",
+ "91.242.215.101",
+ "63.218.156.70",
+ "168.1.1.212",
+ "43.250.205.18",
+ "118.127.62.85"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "af2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "au2-obf-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.208.6"
+ "103.107.197.170",
+ "103.107.197.171",
+ "103.107.197.172",
+ "217.79.116.201",
+ "217.79.116.202",
+ "217.79.116.203"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "ao2-auto-tcp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "au2-tcp-pf.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
"ips": [
- "172.94.84.4",
- "172.94.84.6"
+ "104.37.5.243"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "ao2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "au2-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565
+ ],
+ "obfuscated": true,
"ips": [
- "172.94.84.4",
- "172.94.84.6"
+ "104.37.5.245",
+ "104.37.5.246"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bb2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "hostname": "au2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
"ips": [
- "172.111.228.4",
- "172.111.228.6"
+ "104.37.5.243",
+ "104.37.5.244"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bb2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "au2-udp-qr-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "172.111.228.4",
- "172.111.228.6"
+ "104.37.5.251"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bd2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "hostname": "aubb2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "103.46.140.6"
+ "172.94.100.243",
+ "172.94.100.244"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bd2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "aubn2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "103.46.140.6"
+ "103.62.50.199",
+ "103.62.50.200",
+ "103.62.50.201",
+ "144.48.39.71",
+ "144.48.39.72",
+ "144.48.39.73"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bh2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "hostname": "aubn2-obf-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.226.6"
+ "104.37.5.246",
+ "104.37.5.247"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bh2-auto-udp.ptoserver.com",
- "udp": true,
+ "country": "Australia",
+ "hostname": "aubn2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
"ips": [
- "172.111.226.4"
+ "104.37.5.243"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bm2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "hostname": "aubn2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
"ips": [
- "172.111.229.4",
- "172.111.229.6"
+ "104.37.5.243"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bm2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "hostname": "aubn2-udp-qr-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "172.111.229.4",
- "172.111.229.6"
+ "104.37.5.251"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bs2-auto-tcp.ptoserver.com",
+ "country": "Australia",
+ "city": "Melbourne",
+ "hostname": "aume2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "172.94.20.6"
+ "79.127.155.233",
+ "103.137.15.103"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "bs2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "city": "Melbourne",
+ "hostname": "aume2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.94.20.4",
- "172.94.20.6",
- "172.94.20.37"
+ "79.127.155.233",
+ "103.137.15.103",
+ "79.127.155.232"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "de2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "city": "Melbourne",
+ "hostname": "aume2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "5.254.23.71",
- "149.88.19.5"
+ "79.127.155.232",
+ "103.137.15.103",
+ "79.127.155.233"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "de2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "city": "Melbourne",
+ "hostname": "aume2-obf-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "149.34.252.48",
- "149.88.19.5"
+ "104.37.5.245",
+ "104.37.5.246",
+ "104.37.5.247"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "dz2-auto-tcp.ptoserver.com",
+ "country": "Australia",
+ "city": "Melbourne",
+ "hostname": "aume2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "172.111.243.6",
- "172.111.243.19",
- "172.111.243.21"
+ "79.127.155.233",
+ "103.137.15.103"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "dz2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "city": "Melbourne",
+ "hostname": "aume2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "172.111.243.19",
- "172.111.243.21"
+ "79.127.155.233",
+ "79.127.155.232",
+ "103.137.15.103"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "ru2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "city": "Perth",
+ "hostname": "aupe2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.185.37"
+ "103.107.197.170",
+ "103.107.197.171",
+ "103.107.197.172"
]
},
{
"vpn": "openvpn",
- "country": "Germany",
- "region": "Hesse",
- "city": "Frankfurt am Main",
- "hostname": "ru2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "city": "Perth",
+ "hostname": "aupe2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.111.185.6",
- "172.111.185.21",
- "172.111.185.22",
- "172.111.185.37"
+ "103.107.197.168",
+ "103.107.197.169",
+ "103.107.197.167"
]
},
{
"vpn": "openvpn",
- "country": "Greece",
- "region": "Attica",
- "city": "Athens",
- "hostname": "gr2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "city": "Sydney",
+ "hostname": "ausd2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "194.150.167.180",
- "194.150.167.181"
+ "217.79.116.202",
+ "217.79.116.203",
+ "217.79.116.201"
]
},
{
"vpn": "openvpn",
- "country": "Hong Kong",
- "region": "Central and Western",
- "city": "Hong Kong",
- "hostname": "hk2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Australia",
+ "city": "Sydney",
+ "hostname": "ausd2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "103.109.103.85"
+ "217.79.116.198",
+ "217.79.116.199",
+ "217.79.116.200"
]
},
{
"vpn": "openvpn",
- "country": "Hong Kong",
- "region": "Central and Western",
- "city": "Hong Kong",
- "hostname": "hk2-auto-udp.ptoserver.com",
+ "country": "Australia",
+ "city": "Sydney",
+ "hostname": "ausd2-obf-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "103.109.103.85",
- "103.109.103.87"
+ "104.37.5.246",
+ "104.37.5.247"
]
},
{
"vpn": "openvpn",
- "country": "Hungary",
- "region": "Budapest",
- "city": "Budapest",
- "hostname": "hu2-auto-tcp.ptoserver.com",
+ "country": "Austria",
+ "hostname": "at2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "146.70.120.70"
+ "80.120.176.110",
+ "154.47.19.17",
+ "5.254.83.130"
]
},
{
"vpn": "openvpn",
- "country": "Hungary",
- "region": "Budapest",
- "city": "Budapest",
- "hostname": "hu2-auto-udp.ptoserver.com",
+ "country": "Austria",
+ "hostname": "at2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "146.70.120.71"
+ "149.40.52.206",
+ "149.40.52.207",
+ "149.40.52.205"
]
},
{
"vpn": "openvpn",
- "country": "Indonesia",
- "region": "Jakarta",
- "city": "Jakarta",
- "hostname": "id2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Austria",
+ "hostname": "at2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "103.60.9.132",
- "103.60.9.134"
+ "149.40.52.198",
+ "149.40.52.200"
]
},
{
"vpn": "openvpn",
- "country": "Indonesia",
- "region": "Jakarta",
- "city": "Jakarta",
- "hostname": "id2-auto-udp.ptoserver.com",
+ "country": "Austria",
+ "hostname": "at2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "103.60.9.132",
- "103.60.9.140",
- "103.60.9.142"
+ "80.120.176.110",
+ "154.47.19.17",
+ "5.254.83.130"
]
},
{
"vpn": "openvpn",
- "country": "Ireland",
- "region": "Leinster",
- "city": "Dublin",
- "hostname": "ie2-auto-tcp.ptoserver.com",
+ "country": "Austria",
+ "hostname": "at2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "146.70.130.135",
- "146.70.130.152"
+ "80.120.176.110",
+ "154.47.19.17",
+ "5.254.83.130"
]
},
{
"vpn": "openvpn",
- "country": "Ireland",
- "region": "Leinster",
- "city": "Dublin",
- "hostname": "ie2-auto-udp.ptoserver.com",
+ "country": "Austria",
+ "hostname": "at2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "146.70.130.134",
- "146.70.130.135",
- "146.70.130.151"
+ "80.120.176.110",
+ "154.47.19.17",
+ "5.254.83.130"
]
},
{
"vpn": "openvpn",
- "country": "Italy",
- "region": "Lombardy",
- "city": "Milan",
- "hostname": "it2-auto-tcp.ptoserver.com",
+ "country": "Bahamas",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bs2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "149.50.215.71"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Italy",
- "region": "Lombardy",
- "city": "Milan",
- "hostname": "it2-auto-udp.ptoserver.com",
+ "country": "Bahamas",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bs2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "149.50.215.70"
+ "172.94.28.11",
+ "172.94.28.9",
+ "172.94.28.12"
]
},
{
"vpn": "openvpn",
- "country": "Japan",
- "region": "Tokyo",
- "city": "Tokyo",
- "hostname": "jp2-auto-udp.ptoserver.com",
+ "country": "Bahamas",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bs2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.250.255.9",
- "149.50.210.151"
+ "172.94.28.8",
+ "172.94.28.6",
+ "172.94.28.7"
]
},
{
"vpn": "openvpn",
- "country": "Kenya",
- "region": "Nairobi Area",
- "city": "Nairobi",
- "hostname": "ke2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Bahamas",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bs2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "62.12.116.86",
- "62.12.116.87"
+ "82.102.29.155"
]
},
{
"vpn": "openvpn",
- "country": "Kenya",
- "region": "Nairobi Area",
- "city": "Nairobi",
- "hostname": "ke2-auto-udp.ptoserver.com",
- "udp": true,
+ "country": "Bahamas",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bs2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahamas",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bs2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahrain",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bh2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahrain",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bh2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.29.14",
+ "172.94.29.15",
+ "172.94.29.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahrain",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bh2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.29.10",
+ "172.94.29.12",
+ "172.94.29.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahrain",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bh2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahrain",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bh2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bahrain",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bh2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bangladesh",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bd2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bangladesh",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bd2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "103.46.140.102",
+ "103.46.140.103"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bangladesh",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bd2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "103.46.140.101",
+ "103.46.140.106"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bangladesh",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bd2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bangladesh",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bd2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bangladesh",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bd2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Barbados",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bb2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Barbados",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bb2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.37.9",
+ "172.94.37.10",
+ "172.94.37.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Barbados",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bb2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.37.8",
+ "172.94.37.6",
+ "172.94.37.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Barbados",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bb2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Barbados",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bb2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Barbados",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bb2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "80.239.151.98",
+ "62.115.146.17"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "154.47.27.102",
+ "154.47.27.103",
+ "154.47.27.74",
+ "154.47.27.75",
+ "154.47.27.78",
+ "154.47.27.104",
+ "154.47.27.105",
+ "154.47.27.100"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "154.47.27.97",
+ "154.47.27.73",
+ "154.47.27.99",
+ "154.47.27.72",
+ "154.47.27.98",
+ "154.47.27.71"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "80.239.151.98",
+ "62.115.146.17"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "80.239.151.98",
+ "62.115.146.17"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "146.70.123.76",
+ "146.70.123.77"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "be2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "80.239.151.98",
+ "62.115.146.17"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bermuda",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bm2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bermuda",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bm2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.49.9",
+ "172.94.49.11",
+ "172.94.49.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bermuda",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bm2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.49.7",
+ "172.94.49.6",
+ "172.94.49.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bermuda",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bm2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bermuda",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bm2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bermuda",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bm2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.155"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bolivia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bo2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "146.70.41.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bolivia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bo2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.51.9",
+ "172.94.51.10",
+ "172.94.51.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bolivia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bo2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.51.6",
+ "172.94.51.7",
+ "172.94.51.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bolivia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bo2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "146.70.41.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bolivia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bo2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "146.70.41.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bolivia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bo2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "146.70.41.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "hostname": "br2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "154.54.28.50",
+ "146.19.95.2",
+ "8.243.152.22",
+ "5.53.7.176"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "hostname": "br2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.102.251.9",
+ "149.102.251.10",
+ "177.54.147.158",
+ "149.102.251.11",
+ "177.54.147.157",
+ "177.54.147.159"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "hostname": "br2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.102.251.7",
+ "149.102.251.8",
+ "177.54.147.154",
+ "177.54.147.155",
+ "177.54.147.156"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "hostname": "br2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "154.54.28.50",
+ "146.19.95.2",
+ "8.243.152.22",
+ "5.53.7.176"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "hostname": "br2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "154.54.28.50",
+ "146.19.95.2",
+ "8.243.152.22",
+ "5.53.7.176"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "hostname": "br2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "154.54.28.50",
+ "146.19.95.2",
+ "8.243.152.22",
+ "5.53.7.176"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "British Virgin Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "vg2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "British Virgin Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "vg2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.55.9",
+ "172.94.55.10",
+ "172.94.55.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "British Virgin Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "vg2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.55.6",
+ "172.94.55.7",
+ "172.94.55.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "British Virgin Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "vg2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "British Virgin Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "vg2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "British Virgin Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "vg2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brunei Darussalam",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bn2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "138.199.60.118"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brunei Darussalam",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bn2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.82.9",
+ "172.94.82.10",
+ "172.94.82.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brunei Darussalam",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bn2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.82.6",
+ "172.94.82.7",
+ "172.94.82.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brunei Darussalam",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bn2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "138.199.60.118"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brunei Darussalam",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bn2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "84.247.49.212",
+ "84.247.49.213"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brunei Darussalam",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bn2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "138.199.60.118"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bulgaria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bg2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "194.182.177.178",
+ "185.94.192.130",
+ "212.103.51.133"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bulgaria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bg2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "37.46.114.149",
+ "37.46.114.151",
+ "37.46.114.150"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bulgaria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bg2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "37.46.114.148",
+ "37.46.114.147",
+ "37.46.114.146"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bulgaria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bg2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "194.182.177.178",
+ "185.94.192.130",
+ "212.103.51.133"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bulgaria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bg2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "194.182.177.178",
+ "185.94.192.130",
+ "212.103.51.133"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Bulgaria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "bg2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "194.182.177.178",
+ "185.94.192.130",
+ "212.103.51.133"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "158.85.65.212",
+ "154.54.27.162",
+ "184.75.221.218",
+ "38.88.240.26",
+ "74.199.144.74",
+ "199.167.17.229",
+ "104.200.132.137",
+ "38.32.1.42",
+ "212.103.51.81",
+ "209.127.27.58",
+ "62.115.9.210"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "95.173.219.105",
+ "149.50.222.10",
+ "149.88.98.233"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "95.173.219.102",
+ "149.50.222.7",
+ "149.50.222.8",
+ "149.50.222.9",
+ "149.88.98.242",
+ "149.88.98.229"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "158.85.65.212",
+ "154.54.27.162",
+ "184.75.221.218",
+ "38.88.240.26",
+ "74.199.144.74",
+ "199.167.17.229",
+ "104.200.132.137",
+ "38.32.1.42",
+ "212.103.51.81",
+ "209.127.27.58",
+ "62.115.9.210"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "198.144.155.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "198.144.155.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "ca2-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "95.173.219.102",
+ "149.50.222.8",
+ "149.88.98.229",
+ "149.50.222.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "caq2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "217.138.213.235"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "caq2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "217.138.213.232"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "caq2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "199.167.17.229",
+ "158.85.65.212",
+ "184.75.221.218",
+ "212.103.51.81",
+ "104.200.132.137",
+ "209.127.27.58",
+ "62.115.9.210",
+ "38.32.1.42",
+ "38.88.240.26",
+ "154.54.27.162",
+ "74.199.144.74"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "caq2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "199.167.17.229",
+ "158.85.65.212",
+ "184.75.221.218",
+ "212.103.51.81",
+ "104.200.132.137",
+ "209.127.27.58",
+ "62.115.9.210",
+ "38.32.1.42",
+ "38.88.240.26",
+ "154.54.27.162",
+ "74.199.144.74"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "cato2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.50.222.11",
+ "149.88.98.232",
+ "149.88.98.233",
+ "149.50.222.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "cato2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.50.222.7",
+ "149.50.222.8",
+ "149.50.222.9",
+ "149.88.98.242",
+ "149.88.98.229"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "cato2-obf-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.111.135.244",
+ "172.111.135.245"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "cato2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.111.135.244",
+ "172.111.135.245"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "cav2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "95.173.219.105"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "hostname": "cav2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "95.173.219.102",
+ "149.50.222.7",
+ "149.50.222.8",
+ "149.88.98.229"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Cayman Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ky2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Cayman Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ky2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.111.231.71",
+ "172.111.231.72"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Cayman Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ky2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.111.231.75",
+ "172.111.231.70"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Cayman Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ky2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Cayman Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ky2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Cayman Islands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ky2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "cl2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "104.250.176.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "cl2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "195.86.38.9",
+ "195.86.38.10",
+ "195.86.38.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "cl2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "195.86.38.7",
+ "195.86.38.8",
+ "195.86.38.6"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "cl2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "104.250.176.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "cl2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "104.250.176.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "cl2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "104.250.176.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "China",
+ "hostname": "cn2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.34.253.65",
+ "43.245.63.154"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "China",
+ "hostname": "cn2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.86.138",
+ "172.94.86.139",
+ "172.94.86.137"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "China",
+ "hostname": "cn2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.86.135",
+ "172.94.86.136",
+ "172.94.86.134"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "China",
+ "hostname": "cn2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.34.253.65",
+ "43.245.63.154"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "hostname": "cz2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.6.25.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "hostname": "cz2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "79.127.154.41",
+ "79.127.154.44",
+ "185.216.35.110",
+ "79.127.154.43",
+ "185.216.35.107",
+ "185.216.35.108",
+ "185.216.35.109"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "hostname": "cz2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "79.127.154.38",
+ "185.216.35.104",
+ "185.216.35.105",
+ "185.216.35.106",
+ "79.127.154.39"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "hostname": "cz2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.6.25.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "hostname": "cz2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.6.25.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "hostname": "cz2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.6.25.2"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dk2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "80.239.151.74",
+ "146.70.89.34",
+ "212.73.253.101",
+ "149.6.137.114"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dk2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.50.217.137",
+ "149.50.217.136",
+ "149.50.217.138",
+ "149.50.217.139"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dk2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.50.217.133",
+ "149.50.217.135",
+ "149.50.217.134"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dk2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "80.239.151.74",
+ "146.70.89.34",
+ "212.73.253.101",
+ "149.6.137.114"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dk2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "80.239.151.74",
+ "146.70.89.34",
+ "212.73.253.101",
+ "149.6.137.114"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "dk2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "80.239.151.74",
+ "146.70.89.34",
+ "212.73.253.101",
+ "149.6.137.114"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Egypt",
+ "hostname": "eg2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162",
+ "196.46.189.110",
+ "196.46.189.230"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Egypt",
+ "hostname": "eg2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "45.74.55.8",
+ "45.74.55.9"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Egypt",
+ "hostname": "eg2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "45.74.55.12",
+ "45.74.55.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Egypt",
+ "hostname": "eg2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162",
+ "196.46.189.110",
+ "196.46.189.230"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Egypt",
+ "hostname": "eg2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162",
+ "196.46.189.110",
+ "196.46.189.230"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Egypt",
+ "hostname": "eg2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162",
+ "196.46.189.110",
+ "196.46.189.230"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ee2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "185.174.159.190",
+ "185.174.159.42"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ee2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "95.153.32.107",
+ "95.153.32.108",
+ "95.153.32.110",
+ "95.153.32.106"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ee2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "95.153.32.103",
+ "95.153.32.104",
+ "95.153.32.105"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ee2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "185.174.159.190",
+ "185.174.159.42"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ee2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "185.174.159.190",
+ "185.174.159.42"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ee2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "185.174.159.190",
+ "185.174.159.42"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Finland",
+ "hostname": "fi2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "94.237.10.248",
+ "217.64.32.253"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Finland",
+ "hostname": "fi2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "85.208.3.106",
+ "85.208.3.107",
+ "85.208.3.108",
+ "85.208.3.109"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Finland",
+ "hostname": "fi2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "85.208.3.104",
+ "85.208.3.105"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Finland",
+ "hostname": "fi2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "94.237.10.248",
+ "217.64.32.253"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Finland",
+ "hostname": "fi2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "94.237.10.248",
+ "217.64.32.253"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Finland",
+ "hostname": "fi2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "94.237.10.248",
+ "217.64.32.253"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "156.146.63.65",
+ "212.103.51.121",
+ "62.115.48.201",
+ "149.6.161.58"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "37.19.217.97",
+ "149.34.245.201",
+ "149.34.245.202",
+ "37.19.217.100",
+ "149.34.245.203",
+ "37.19.217.98"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.34.245.197",
+ "37.19.217.87",
+ "149.34.245.199",
+ "37.19.217.90",
+ "149.34.245.198"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "156.146.63.65",
+ "212.103.51.121",
+ "62.115.48.201",
+ "149.6.161.58"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "156.146.63.65",
+ "212.103.51.121",
+ "62.115.48.201",
+ "149.6.161.58"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "146.70.220.134",
+ "146.70.220.135"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "156.146.63.65",
+ "212.103.51.121",
+ "62.115.48.201",
+ "149.6.161.58"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "hostname": "fr2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "146.70.220.136"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.178.32.238",
+ "82.102.29.155",
+ "5.254.23.66",
+ "154.47.24.244",
+ "5.254.23.178",
+ "138.199.38.33",
+ "140.82.37.249"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "5.254.23.76",
+ "149.34.252.53",
+ "149.88.19.7",
+ "149.88.19.10",
+ "5.254.23.74",
+ "5.254.23.75",
+ "5.254.23.77",
+ "5.254.23.80",
+ "79.127.156.9",
+ "149.88.19.8",
+ "203.23.178.179",
+ "79.127.156.10",
+ "203.23.179.44",
+ "149.88.19.11",
+ "203.23.179.43"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "5.254.23.73",
+ "149.34.252.8",
+ "149.34.252.51",
+ "203.23.179.42",
+ "79.127.156.8",
+ "203.23.179.24",
+ "203.23.179.25",
+ "203.23.179.41",
+ "149.88.19.7",
+ "203.23.179.26",
+ "203.23.179.40"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "82.178.32.238",
+ "82.102.29.155",
+ "5.254.23.66",
+ "154.47.24.244",
+ "5.254.23.178",
+ "138.199.38.33",
+ "140.82.37.249"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "hostname": "de2-obf-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "185.232.23.102",
+ "185.232.23.103"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "82.178.32.238",
+ "82.102.29.155",
+ "5.254.23.66",
+ "154.47.24.244",
+ "5.254.23.178",
+ "138.199.38.33",
+ "140.82.37.249"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565
+ ],
+ "obfuscated": true,
+ "ips": [
+ "185.232.23.102",
+ "185.232.23.103"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "82.178.32.238",
+ "82.102.29.155",
+ "5.254.23.66",
+ "154.47.24.244",
+ "5.254.23.178",
+ "138.199.38.33",
+ "140.82.37.249"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "de2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "185.232.23.109",
+ "185.232.23.117"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "hostname": "gr2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "62.233.127.182",
+ "194.150.167.140"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "hostname": "gr2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "79.127.181.244",
+ "149.22.85.136",
+ "79.127.181.243",
+ "79.127.181.245",
+ "149.22.85.161",
+ "149.22.85.135",
+ "149.22.85.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "hostname": "gr2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "79.127.181.230",
+ "79.127.181.239",
+ "79.127.181.240",
+ "79.127.181.241",
+ "149.22.85.133",
+ "79.127.181.232",
+ "149.22.85.134"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "hostname": "gr2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "62.233.127.182",
+ "194.150.167.140"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "hostname": "gr2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "62.233.127.182",
+ "194.150.167.140"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "hostname": "gr2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "62.233.127.182",
+ "194.150.167.140"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "103.109.103.52"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "103.55.10.12",
+ "103.55.10.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "103.55.10.3",
+ "103.55.10.4",
+ "103.55.10.5"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "103.109.103.52"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "103.109.103.52"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "45.115.25.10",
+ "45.115.25.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "103.109.103.52"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "hostname": "hk2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "45.115.25.6"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "hostname": "hu2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "134.0.219.214",
+ "193.9.115.129"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "hostname": "hu2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "212.11.29.1",
+ "212.11.29.2",
+ "152.233.4.243"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "hostname": "hu2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "152.233.4.240",
+ "152.233.4.241",
+ "152.233.4.242"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "hostname": "hu2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "134.0.219.214",
+ "193.9.115.129"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "hostname": "hu2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "134.0.219.214",
+ "193.9.115.129"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "hostname": "hu2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "134.0.219.214",
+ "193.9.115.129"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "146.70.41.34",
+ "149.34.253.65",
+ "82.102.29.155",
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.41.7",
+ "172.94.41.8",
+ "172.111.170.8",
+ "206.123.159.9",
+ "172.111.170.9",
+ "206.123.159.10",
+ "206.123.159.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.41.11",
+ "172.111.170.7",
+ "206.123.159.6",
+ "172.94.41.6",
+ "172.111.170.12",
+ "206.123.159.7",
+ "206.123.159.19"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "146.70.41.34",
+ "149.34.253.65",
+ "82.102.29.155",
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "149.34.253.95",
+ "149.34.253.96"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.34.253.103",
+ "149.34.253.104",
+ "149.34.253.105"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "149.34.253.95",
+ "149.34.253.96",
+ "149.34.253.97"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "149.34.253.102"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "hostname": "in2-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "146.70.41.34",
+ "149.34.253.65",
+ "82.102.29.155",
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ireland",
+ "hostname": "ie2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.14.149.154",
+ "149.11.36.90"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ireland",
+ "hostname": "ie2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.34.243.225",
+ "77.81.139.202",
+ "77.81.139.203",
+ "77.81.139.204",
+ "77.81.139.205",
+ "149.34.243.9",
+ "149.34.243.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ireland",
+ "hostname": "ie2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "149.14.149.154",
+ "149.11.36.90"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ireland",
+ "hostname": "ie2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.14.149.154",
+ "149.11.36.90"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ireland",
+ "hostname": "ie2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.14.149.154",
+ "149.11.36.90"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "62.115.166.253",
+ "149.50.215.65",
+ "82.178.32.234"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "135.136.2.13",
+ "149.50.215.77",
+ "149.50.215.78",
+ "135.136.2.11",
+ "135.136.2.12",
+ "149.50.215.75"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "135.136.2.9",
+ "135.136.2.10",
+ "149.50.215.72",
+ "149.50.215.73",
+ "149.50.215.71"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "62.115.166.253",
+ "149.50.215.65",
+ "82.178.32.234"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "62.115.166.253",
+ "149.50.215.65",
+ "82.178.32.234"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "146.70.109.172",
+ "146.70.109.173"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "62.115.166.253",
+ "149.50.215.65",
+ "82.178.32.234"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "hostname": "it2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "146.70.109.171"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "45.250.255.4",
+ "202.84.148.70",
+ "45.76.105.233",
+ "199.254.199.44"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "45.250.255.16",
+ "45.250.255.17",
+ "45.250.255.18"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "45.250.255.9",
+ "45.250.255.10",
+ "45.250.255.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "45.250.255.4",
+ "202.84.148.70",
+ "45.76.105.233",
+ "199.254.199.44"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "45.250.255.4",
+ "202.84.148.70",
+ "45.76.105.233",
+ "199.254.199.44"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.79.244",
+ "172.94.79.245"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "45.250.255.4",
+ "202.84.148.70",
+ "45.76.105.233",
+ "199.254.199.44"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "hostname": "jp2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "172.111.189.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Korea",
+ "hostname": "kr2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "141.164.48.186",
+ "113.29.48.54",
+ "62.233.127.182",
+ "34.64.47.179",
+ "172.107.245.21",
+ "27.102.106.237"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Korea",
+ "hostname": "kr2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "108.181.50.76",
+ "108.181.50.75",
+ "108.181.50.184",
+ "108.181.50.185"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Korea",
+ "hostname": "kr2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "108.181.50.74",
+ "108.181.50.164",
+ "108.181.50.189",
+ "108.181.52.157",
+ "108.181.50.183"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Korea",
+ "hostname": "kr2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "141.164.48.186",
+ "113.29.48.54",
+ "62.233.127.182",
+ "34.64.47.179",
+ "172.107.245.21",
+ "27.102.106.237"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Korea",
+ "hostname": "kr2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "141.164.48.186",
+ "113.29.48.54",
+ "62.233.127.182",
+ "34.64.47.179",
+ "172.107.245.21",
+ "27.102.106.237"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Korea",
+ "hostname": "kr2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "141.164.48.186",
+ "113.29.48.54",
+ "62.233.127.182",
+ "34.64.47.179",
+ "172.107.245.21",
+ "27.102.106.237"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Latvia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lv2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "62.233.127.182",
+ "193.68.78.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Latvia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lv2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "185.135.87.41",
+ "185.135.87.42",
+ "185.135.87.108",
+ "185.135.87.43",
+ "185.135.87.107",
+ "185.135.87.109",
+ "185.135.87.40"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Latvia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lv2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "185.135.87.37",
+ "185.135.87.38",
+ "185.135.87.39",
+ "185.135.87.106",
+ "185.135.87.105"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Latvia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lv2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "62.233.127.182",
+ "193.68.78.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Latvia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lv2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "62.233.127.182",
+ "193.68.78.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Latvia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lv2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "62.233.127.182",
+ "193.68.78.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Lithuania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lt2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "213.226.128.131",
+ "184.104.192.78"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Lithuania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lt2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565
+ ],
+ "obfuscated": true,
+ "ips": [
+ "195.238.124.169",
+ "195.238.124.170",
+ "195.238.124.185",
+ "195.238.124.184",
+ "195.238.124.186"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Lithuania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lt2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "195.238.124.181",
+ "195.238.124.182",
+ "195.238.124.183"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Lithuania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lt2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "213.226.128.131",
+ "184.104.192.78"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Lithuania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lt2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "213.226.128.131",
+ "184.104.192.78"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Lithuania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lt2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "213.226.128.131",
+ "184.104.192.78"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Luxembourg",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lu2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "185.153.151.1",
+ "213.144.176.232",
+ "185.153.151.85"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Luxembourg",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lu2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "85.90.194.14",
+ "85.90.194.16",
+ "95.85.66.238",
+ "95.85.66.239",
+ "85.90.194.15"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Luxembourg",
+ "hostname": "lu2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "95.85.66.237",
+ "85.90.194.11",
+ "95.85.66.235",
+ "85.90.194.12"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Luxembourg",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lu2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "185.153.151.1",
+ "213.144.176.232",
+ "185.153.151.85"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Luxembourg",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lu2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "185.153.151.1",
+ "213.144.176.232",
+ "185.153.151.85"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Luxembourg",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "lu2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "185.153.151.1",
+ "213.144.176.232",
+ "185.153.151.85"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "md2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "178.175.130.26",
+ "193.239.165.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "md2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "178.175.143.234",
+ "178.175.143.236",
+ "178.175.143.235"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "md2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "178.175.143.231",
+ "178.175.143.233",
+ "178.175.143.232"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "md2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "178.175.130.26",
+ "193.239.165.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "md2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "178.175.130.26",
+ "193.239.165.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "md2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "178.175.130.26",
+ "193.239.165.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Monaco",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "mc2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Monaco",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "mc2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "206.123.130.8",
+ "206.123.130.9"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Monaco",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "mc2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "206.123.130.7",
+ "206.123.130.12"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Monaco",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "mc2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Monaco",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "mc2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Monaco",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "mc2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "37.46.122.79",
+ "149.14.143.114",
+ "185.254.68.29",
+ "5.254.70.210",
+ "213.19.196.242",
+ "31.220.40.21",
+ "2.58.44.162",
+ "5.254.70.162",
+ "193.228.196.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.40.59.79",
+ "203.17.245.202",
+ "203.17.245.203",
+ "149.40.59.77",
+ "149.40.59.78",
+ "149.40.59.80",
+ "203.17.245.204",
+ "5.254.26.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "203.17.245.199",
+ "149.40.59.70",
+ "149.40.59.71",
+ "203.17.245.200"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "37.46.122.79",
+ "149.14.143.114",
+ "185.254.68.29",
+ "5.254.70.210",
+ "213.19.196.242",
+ "31.220.40.21",
+ "2.58.44.162",
+ "5.254.70.162",
+ "193.228.196.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-obf-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "5.254.15.99",
+ "5.254.15.100",
+ "5.254.15.101"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "37.46.122.79",
+ "149.14.143.114",
+ "185.254.68.29",
+ "5.254.70.210",
+ "213.19.196.242",
+ "31.220.40.21",
+ "2.58.44.162",
+ "5.254.70.162",
+ "193.228.196.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "37.46.122.79",
+ "149.14.143.114",
+ "185.254.68.29",
+ "5.254.70.210",
+ "213.19.196.242",
+ "31.220.40.21",
+ "2.58.44.162",
+ "5.254.70.162",
+ "193.228.196.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "nl2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "5.254.15.106",
+ "5.254.15.115"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "New Zealand",
+ "hostname": "nz2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "221.121.135.230",
+ "103.97.53.51"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "New Zealand",
+ "hostname": "nz2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "89.222.126.171",
+ "89.222.126.172",
+ "203.209.219.47",
+ "89.222.126.170",
+ "203.209.219.48"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "New Zealand",
+ "hostname": "nz2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "89.222.126.168",
+ "89.222.126.169",
+ "203.209.219.40",
+ "89.222.126.167",
+ "203.209.219.41",
+ "203.209.219.42"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "New Zealand",
+ "hostname": "nz2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "221.121.135.230",
+ "103.97.53.51"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "New Zealand",
+ "hostname": "nz2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "221.121.135.230",
+ "103.97.53.51"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "New Zealand",
+ "hostname": "nz2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "221.121.135.230",
+ "103.97.53.51"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Nigeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ng2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Nigeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ng2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.111.128.39",
+ "172.111.128.40"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Nigeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ng2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.111.128.38",
+ "172.111.128.43"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Nigeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ng2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Nigeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ng2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Nigeria",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ng2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "no2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "154.54.58.226",
+ "185.125.170.119"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "no2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "146.70.170.42",
+ "146.70.170.43",
+ "146.70.170.44"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "no2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "146.70.170.39",
+ "146.70.170.40",
+ "146.70.170.41"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "no2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "154.54.58.226",
+ "185.125.170.119"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "no2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "154.54.58.226",
+ "185.125.170.119"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "no2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "154.54.58.226",
+ "185.125.170.119"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Oman",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "om2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Oman",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "om2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.94.7",
+ "172.94.94.9",
+ "172.94.94.38",
+ "172.94.94.40",
+ "172.94.94.8",
+ "172.94.94.39"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Oman",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "om2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.94.42",
+ "172.94.94.6",
+ "172.94.94.13",
+ "172.94.94.37"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Oman",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "om2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Oman",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "om2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Oman",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "om2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pa2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pa2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.87.39",
+ "172.94.87.40"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pa2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.87.38",
+ "172.94.87.43"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pa2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pa2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pa2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Philippines",
+ "hostname": "ph2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "172.111.175.2",
+ "149.34.253.65"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Philippines",
+ "hostname": "ph2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.94.119.10",
+ "172.94.119.11",
+ "172.94.119.9"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Philippines",
+ "hostname": "ph2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.94.119.6",
+ "172.94.119.8",
+ "172.94.119.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Philippines",
+ "hostname": "ph2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "172.111.175.2",
+ "149.34.253.65"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Philippines",
+ "hostname": "ph2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "172.111.175.2",
+ "149.34.253.65"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Philippines",
+ "hostname": "ph2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "172.111.175.2",
+ "149.34.253.65"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "hostname": "pl2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.245",
+ "149.102.244.33",
+ "70.34.251.47"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "hostname": "pl2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.102.244.41",
+ "149.102.244.42",
+ "149.102.244.43",
+ "146.70.232.235",
+ "149.102.244.44",
+ "149.102.244.45",
+ "149.102.244.46"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "hostname": "pl2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "146.70.232.231",
+ "146.70.232.232",
+ "149.102.244.39",
+ "149.102.244.40",
+ "149.102.244.38"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "hostname": "pl2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.245",
+ "149.102.244.33",
+ "70.34.251.47"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "hostname": "pl2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "82.102.29.245",
+ "149.102.244.33",
+ "70.34.251.47"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "hostname": "pl2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "82.102.29.245",
+ "149.102.244.33",
+ "70.34.251.47"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Portugal",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pt2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.88.20.129",
+ "62.233.127.182",
+ "149.102.244.33",
+ "185.166.144.243"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Portugal",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pt2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "185.174.156.38",
+ "185.174.156.39",
+ "185.174.156.40"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Portugal",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pt2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "185.174.156.34",
+ "185.174.156.37"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Portugal",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pt2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.88.20.129",
+ "62.233.127.182",
+ "149.102.244.33",
+ "185.166.144.243"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Portugal",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pt2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.88.20.129",
+ "62.233.127.182",
+ "149.102.244.33",
+ "185.166.144.243"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Portugal",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pt2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1521
+ ],
+ "ips": [
+ "149.88.20.129",
+ "62.233.127.182",
+ "149.102.244.33",
+ "185.166.144.243"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Puerto Rico",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pr2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Puerto Rico",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pr2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "104.250.183.8",
+ "104.250.183.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Puerto Rico",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pr2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "104.250.183.6",
+ "104.250.183.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Puerto Rico",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pr2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Puerto Rico",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pr2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Puerto Rico",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "pr2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ro2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.102.239.65",
+ "146.70.124.2",
+ "37.221.171.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ro2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "66.234.147.134",
+ "66.234.147.136",
+ "66.234.147.150",
+ "66.234.147.152",
+ "66.234.147.151",
+ "66.234.147.135"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ro2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "66.234.147.131",
+ "66.234.147.132",
+ "66.234.147.133",
+ "66.234.147.149",
+ "66.234.147.148"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ro2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.102.239.65",
+ "146.70.124.2",
+ "37.221.171.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ro2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.102.239.65",
+ "146.70.124.2",
+ "37.221.171.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ro2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 53
+ ],
+ "ips": [
+ "149.102.239.65",
+ "146.70.124.2",
+ "37.221.171.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "149.11.89.194",
+ "31.28.19.227",
+ "82.102.29.155",
+ "82.102.29.245",
+ "92.38.136.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "45.74.22.8",
+ "45.74.22.9",
+ "45.74.22.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "45.74.22.5",
+ "45.74.22.6",
+ "45.74.22.7",
+ "172.111.185.37",
+ "172.111.185.43",
+ "172.111.185.75"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.11.89.194",
+ "31.28.19.227",
+ "82.102.29.155",
+ "82.102.29.245",
+ "92.38.136.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "149.11.89.194",
+ "31.28.19.227",
+ "82.102.29.155",
+ "82.102.29.245",
+ "92.38.136.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "45.74.22.8",
+ "45.74.22.9",
+ "45.74.22.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "149.11.89.194",
+ "31.28.19.227",
+ "82.102.29.155",
+ "82.102.29.245",
+ "92.38.136.1"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ru2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "45.74.22.5",
+ "45.74.22.6",
+ "45.74.22.7",
+ "172.111.185.37",
+ "172.111.185.43",
+ "172.111.185.75"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "rs2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "212.103.51.181"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "rs2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "37.46.115.196",
+ "152.89.160.70",
+ "37.46.115.197",
+ "152.89.160.72",
+ "37.46.115.198",
+ "37.46.115.199",
+ "152.89.160.71"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "rs2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "37.46.115.194",
+ "152.89.160.69",
+ "37.46.115.193",
+ "152.89.160.67",
+ "152.89.160.68",
+ "37.46.115.195"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "rs2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "212.103.51.181"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "rs2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "212.103.51.181"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "rs2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "212.103.51.181"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "hostname": "sg2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "94.237.73.82",
+ "146.70.67.194",
+ "149.34.253.65",
+ "43.245.63.154",
+ "119.81.28.170",
+ "84.247.49.210",
+ "210.176.138.135"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "hostname": "sg2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "103.107.199.171",
+ "149.50.211.72",
+ "149.50.211.74",
+ "74.63.203.59",
+ "103.107.199.170",
+ "149.50.211.75",
+ "74.63.203.58",
+ "103.107.199.172",
+ "103.107.199.173"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "hostname": "sg2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "74.63.203.56",
+ "149.50.211.70",
+ "74.63.203.57",
+ "103.107.199.167",
+ "103.107.199.168",
+ "103.107.199.169",
+ "149.50.211.71",
+ "149.50.211.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "hostname": "sg2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "94.237.73.82",
+ "146.70.67.194",
+ "149.34.253.65",
+ "43.245.63.154",
+ "119.81.28.170",
+ "84.247.49.210",
+ "210.176.138.135"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "hostname": "sg2-obf-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "62.12.116.87"
+ "206.123.135.8",
+ "206.123.135.9",
+ "206.123.135.10"
]
},
{
"vpn": "openvpn",
- "country": "Korea",
- "region": "Seoul",
- "city": "Seoul",
- "hostname": "kr2-auto-udp.ptoserver.com",
+ "country": "Singapore",
+ "hostname": "sg2-tcp-pf.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
+ "ips": [
+ "94.237.73.82",
+ "146.70.67.194",
+ "149.34.253.65",
+ "43.245.63.154",
+ "119.81.28.170",
+ "84.247.49.210",
+ "210.176.138.135"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "hostname": "sg2-udp-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
"ips": [
- "108.181.50.166",
- "108.181.50.182",
- "108.181.50.183",
- "108.181.52.151"
+ "94.237.73.82",
+ "146.70.67.194",
+ "149.34.253.65",
+ "43.245.63.154",
+ "119.81.28.170",
+ "84.247.49.210",
+ "210.176.138.135"
]
},
{
"vpn": "openvpn",
- "country": "Latvia",
- "region": "Riga",
- "city": "Riga",
- "hostname": "lv2-auto-tcp.ptoserver.com",
+ "country": "Singapore",
+ "hostname": "sg2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 53
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "206.123.135.6",
+ "206.123.135.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Slovakia",
+ "hostname": "sk2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "213.21.209.36",
- "213.21.209.37",
- "213.21.215.165"
+ "149.6.174.74"
]
},
{
"vpn": "openvpn",
- "country": "Latvia",
- "region": "Riga",
- "city": "Riga",
- "hostname": "lv2-auto-udp.ptoserver.com",
+ "country": "Slovakia",
+ "hostname": "sk2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "213.21.209.37"
+ "217.79.119.40",
+ "217.79.119.41"
]
},
{
"vpn": "openvpn",
- "country": "Lithuania",
- "region": "Siauliai",
- "city": "Šiauliai",
- "hostname": "lt2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Slovakia",
+ "hostname": "sk2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "5.199.169.37",
- "5.199.169.47",
- "5.199.169.70",
- "185.150.118.37"
+ "217.79.119.39",
+ "217.79.119.37",
+ "217.79.119.38"
]
},
{
"vpn": "openvpn",
- "country": "Lithuania",
- "region": "Siauliai",
- "city": "Šiauliai",
- "hostname": "lt2-auto-udp.ptoserver.com",
+ "country": "Slovakia",
+ "hostname": "sk2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "5.199.169.5",
- "5.199.169.47",
- "5.199.169.70",
- "5.199.169.71"
+ "149.6.174.74"
]
},
{
"vpn": "openvpn",
- "country": "Mexico",
- "region": "Querétaro",
- "city": "Santiago de Querétaro",
- "hostname": "mx2-auto-tcp.ptoserver.com",
+ "country": "Slovakia",
+ "hostname": "sk2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "69.67.148.131",
- "69.67.148.154"
+ "149.6.174.74"
]
},
{
"vpn": "openvpn",
- "country": "Mexico",
- "region": "Querétaro",
- "city": "Santiago de Querétaro",
- "hostname": "mx2-auto-udp.ptoserver.com",
+ "country": "Slovakia",
+ "hostname": "sk2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "69.67.148.132",
- "69.67.148.155"
+ "149.6.174.74"
]
},
{
"vpn": "openvpn",
- "country": "Moldova",
- "region": "Chișinău Municipality",
- "city": "Chisinau",
- "hostname": "md2-auto-tcp.ptoserver.com",
+ "country": "South Africa",
+ "hostname": "za2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "178.17.169.228",
- "178.17.169.229",
- "178.17.169.241"
+ "102.130.70.58",
+ "168.209.1.188"
]
},
{
"vpn": "openvpn",
- "country": "Moldova",
- "region": "Chișinău Municipality",
- "city": "Chisinau",
- "hostname": "md2-auto-udp.ptoserver.com",
+ "country": "South Africa",
+ "hostname": "za2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "178.17.169.241"
+ "154.47.30.170",
+ "154.47.30.172",
+ "154.47.30.173",
+ "213.139.10.106",
+ "154.47.30.171",
+ "213.139.10.105",
+ "213.139.10.107"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "eg2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "South Africa",
+ "hostname": "za2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.74.55.6"
+ "154.47.30.168",
+ "213.139.10.102",
+ "213.139.10.103",
+ "213.139.10.104",
+ "154.47.30.167",
+ "154.47.30.169"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "eg2-auto-udp.ptoserver.com",
+ "country": "South Africa",
+ "hostname": "za2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
"ips": [
- "45.74.55.4"
+ "102.130.70.58",
+ "168.209.1.188"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "is2-auto-tcp.ptoserver.com",
+ "country": "South Africa",
+ "hostname": "za2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "102.130.70.58",
+ "168.209.1.188"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "South Africa",
+ "hostname": "za2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "192.253.250.132",
- "192.253.250.133"
+ "102.130.70.58",
+ "168.209.1.188"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "mc2-auto-tcp.ptoserver.com",
+ "country": "Spain",
+ "hostname": "es2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "206.123.130.4",
- "206.123.130.6"
+ "82.102.29.83",
+ "82.178.33.201",
+ "4.69.140.2",
+ "65.20.97.8"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "mc2-auto-udp.ptoserver.com",
+ "country": "Spain",
+ "hostname": "es2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "206.123.130.6"
+ "45.134.213.17",
+ "62.93.179.233",
+ "62.93.179.235",
+ "135.136.4.11",
+ "45.134.213.15",
+ "45.134.213.16",
+ "62.93.179.234",
+ "135.136.4.12",
+ "45.134.213.18"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "ng2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Spain",
+ "hostname": "es2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.111.128.228"
+ "62.93.179.232",
+ "135.136.4.7",
+ "135.136.4.8",
+ "45.134.213.7",
+ "45.134.213.8",
+ "62.93.179.230",
+ "62.93.179.231",
+ "135.136.4.9",
+ "45.134.213.9"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "ng2-auto-udp.ptoserver.com",
+ "country": "Spain",
+ "hostname": "es2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
"ips": [
- "172.111.128.228",
- "172.111.128.229"
+ "82.102.29.83",
+ "82.178.33.201",
+ "4.69.140.2",
+ "65.20.97.8"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "nl2-auto-tcp.ptoserver.com",
+ "country": "Spain",
+ "hostname": "es2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "37.46.122.74",
- "185.2.30.215",
- "195.181.172.163",
- "195.181.172.164"
+ "82.102.29.83",
+ "82.178.33.201",
+ "4.69.140.2",
+ "65.20.97.8"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "nl2-auto-udp.ptoserver.com",
+ "country": "Spain",
+ "hostname": "es2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "5.254.73.203",
- "37.46.122.71",
- "37.46.122.72",
- "149.34.244.230"
+ "82.102.29.83",
+ "82.178.33.201",
+ "4.69.140.2",
+ "65.20.97.8"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "om2-auto-tcp.ptoserver.com",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "172.94.94.5"
+ "149.11.77.50",
+ "193.27.15.235",
+ "70.34.206.112",
+ "62.115.163.50"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "om2-auto-udp.ptoserver.com",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.94.94.5"
+ "79.142.77.212",
+ "169.150.208.46",
+ "169.150.208.48",
+ "79.142.77.213",
+ "79.142.77.214",
+ "169.150.208.49",
+ "79.142.77.211"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "pa2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.94.87.6",
- "172.94.87.67",
- "172.94.87.69"
+ "79.142.77.210",
+ "169.150.208.36",
+ "169.150.208.38",
+ "79.142.77.208",
+ "169.150.208.37"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "pa2-auto-udp.ptoserver.com",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "172.94.87.4",
- "172.94.87.6",
- "172.94.87.69"
+ "149.11.77.50",
+ "193.27.15.235",
+ "70.34.206.112",
+ "62.115.163.50"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "pr2-auto-tcp.ptoserver.com",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-tcp-pf.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
"ips": [
- "104.250.183.4"
+ "149.11.77.50",
+ "193.27.15.235",
+ "70.34.206.112",
+ "62.115.163.50"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "pr2-auto-udp.ptoserver.com",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "104.250.183.4"
+ "206.123.137.7",
+ "206.123.137.8",
+ "172.111.212.9",
+ "172.111.212.10"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "ua2-auto-tcp.ptoserver.com",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "149.11.77.50",
+ "193.27.15.235",
+ "70.34.206.112",
+ "62.115.163.50"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Sweden",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "se2-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.11.77.50",
+ "193.27.15.235",
+ "70.34.206.112",
+ "62.115.163.50"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "hostname": "ch2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "188.72.101.8",
- "188.72.101.9"
+ "149.6.25.2",
+ "130.117.48.74",
+ "149.11.89.194"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "ua2-auto-udp.ptoserver.com",
+ "country": "Switzerland",
+ "hostname": "ch2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "188.72.101.8",
- "188.72.101.9"
+ "149.102.238.141",
+ "149.102.238.142",
+ "149.102.238.143",
+ "82.102.24.170",
+ "82.102.24.171"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "vg2-auto-tcp.ptoserver.com",
+ "country": "Switzerland",
+ "hostname": "ch2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.102.238.136"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "hostname": "ch2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "149.6.25.2",
+ "130.117.48.74",
+ "149.11.89.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "hostname": "ch2-tcp-pf.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
"ips": [
- "104.243.253.4",
- "104.243.253.6"
+ "149.6.25.2",
+ "130.117.48.74",
+ "149.11.89.194"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "vg2-auto-udp.ptoserver.com",
+ "country": "Switzerland",
+ "hostname": "ch2-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "149.6.25.2",
+ "130.117.48.74",
+ "149.11.89.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "hostname": "ch2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "ips": [
+ "149.6.25.2",
+ "130.117.48.74",
+ "149.11.89.194"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "hostname": "ch2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
+ "ips": [
+ "149.102.238.136"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "hostname": "ch2-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "149.102.238.136"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Taiwan",
+ "hostname": "tw2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "104.243.253.4",
- "104.243.253.6"
+ "43.228.156.5"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "vn2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Taiwan",
+ "hostname": "tw2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.197.6",
- "172.111.197.69"
+ "43.228.156.39",
+ "43.228.156.7",
+ "43.228.156.8"
]
},
{
"vpn": "openvpn",
- "country": "Netherlands",
- "region": "North Holland",
- "city": "Amsterdam",
- "hostname": "vn2-auto-udp.ptoserver.com",
+ "country": "Taiwan",
+ "hostname": "tw2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.111.197.6",
- "172.111.197.68"
+ "43.228.156.6",
+ "43.228.156.36"
]
},
{
"vpn": "openvpn",
- "country": "New Zealand",
- "region": "Auckland",
- "city": "Auckland",
- "hostname": "nz2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Taiwan",
+ "hostname": "tw2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "203.209.219.5",
- "203.209.219.40"
+ "43.228.156.5"
]
},
{
"vpn": "openvpn",
- "country": "Norway",
- "region": "Oslo",
- "city": "Oslo",
- "hostname": "no2-auto-tcp.ptoserver.com",
+ "country": "Taiwan",
+ "hostname": "tw2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "146.70.170.38"
+ "43.228.156.5"
]
},
{
"vpn": "openvpn",
- "country": "Norway",
- "region": "Oslo",
- "city": "Oslo",
- "hostname": "no2-auto-udp.ptoserver.com",
+ "country": "Taiwan",
+ "hostname": "tw2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "146.70.170.39",
- "146.70.170.40"
+ "43.228.156.5"
]
},
{
"vpn": "openvpn",
- "country": "Poland",
- "region": "Mazovia",
- "city": "Warsaw",
- "hostname": "pl2-auto-udp.ptoserver.com",
- "udp": true,
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "149.102.244.37",
- "149.102.244.38"
+ "195.175.203.86"
]
},
{
"vpn": "openvpn",
- "country": "Portugal",
- "region": "Lisbon",
- "city": "Lisbon",
- "hostname": "lu2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "185.153.151.4",
- "185.153.151.6"
+ "149.50.208.103",
+ "149.50.208.105",
+ "149.50.208.104"
]
},
{
"vpn": "openvpn",
- "country": "Portugal",
- "region": "Lisbon",
- "city": "Lisbon",
- "hostname": "lu2-auto-udp.ptoserver.com",
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "185.153.151.6"
+ "149.50.208.100",
+ "149.50.208.102",
+ "149.50.208.101"
]
},
{
"vpn": "openvpn",
- "country": "Portugal",
- "region": "Lisbon",
- "city": "Lisbon",
- "hostname": "pt2-auto-udp.ptoserver.com",
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "149.88.20.134",
- "149.88.20.135"
+ "195.175.203.86"
]
},
{
"vpn": "openvpn",
- "country": "Romania",
- "region": "București",
- "city": "Bucharest",
- "hostname": "ro2-auto-tcp.ptoserver.com",
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-tcp-pf.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
"ips": [
- "149.102.239.70",
- "149.102.239.71"
+ "195.175.203.86"
]
},
{
"vpn": "openvpn",
- "country": "Romania",
- "region": "București",
- "city": "Bucharest",
- "hostname": "ro2-auto-udp.ptoserver.com",
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "149.102.239.70",
- "149.102.239.71"
+ "172.94.13.9",
+ "172.94.13.10"
]
},
{
"vpn": "openvpn",
- "country": "Serbia",
- "region": "Central Serbia",
- "city": "Belgrade",
- "hostname": "rs2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
"ips": [
- "146.70.111.134",
- "146.70.111.135"
+ "195.175.203.86"
]
},
{
"vpn": "openvpn",
- "country": "Serbia",
- "region": "Central Serbia",
- "city": "Belgrade",
- "hostname": "rs2-auto-udp.ptoserver.com",
+ "country": "Turkey",
+ "hostname": "tr2-udp-qr-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "146.70.111.134",
- "146.70.111.135"
+ "172.94.13.6"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "bn2-auto-udp.ptoserver.com",
+ "country": "Turkey",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "tr2-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.74.10.7",
- "45.74.10.21"
+ "172.94.13.6"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "in2-auto-tcp.ptoserver.com",
+ "country": "Ukraine",
+ "hostname": "ua2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "138.199.60.100",
- "149.34.253.68",
- "149.34.253.70"
+ "87.245.237.242"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "in2-auto-udp.ptoserver.com",
+ "country": "Ukraine",
+ "hostname": "ua2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "138.199.60.99",
- "138.199.60.100"
+ "149.102.240.168",
+ "149.102.240.169",
+ "149.102.240.170"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "ph2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "Ukraine",
+ "hostname": "ua2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.111.175.6",
- "172.111.175.7"
+ "149.102.240.165"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "ph2-auto-udp.ptoserver.com",
+ "country": "Ukraine",
+ "hostname": "ua2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "172.111.175.7"
+ "87.245.237.242"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "sg2-auto-tcp.ptoserver.com",
+ "country": "Ukraine",
+ "hostname": "ua2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "149.50.211.55",
- "149.50.211.68",
- "149.50.211.69"
+ "87.245.237.242"
]
},
{
"vpn": "openvpn",
- "country": "Singapore",
- "region": "Singapore",
- "city": "Singapore",
- "hostname": "sg2-auto-udp.ptoserver.com",
+ "country": "Ukraine",
+ "hostname": "ua2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "146.70.67.198",
- "146.70.67.199"
+ "87.245.237.242"
]
},
{
"vpn": "openvpn",
- "country": "Slovakia",
- "region": "Bratislavský Kraj",
- "city": "Bratislava",
- "hostname": "sk2-auto-tcp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "138.199.34.197",
- "138.199.34.198"
+ "146.70.155.2",
+ "38.54.76.28",
+ "212.3.233.134",
+ "38.60.202.210"
]
},
{
"vpn": "openvpn",
- "country": "Slovakia",
- "region": "Bratislavský Kraj",
- "city": "Bratislava",
- "hostname": "sk2-auto-udp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "138.199.34.197"
+ "176.125.231.187",
+ "176.125.231.188",
+ "176.125.231.186"
]
},
{
"vpn": "openvpn",
- "country": "South Africa",
- "region": "Gauteng",
- "city": "Johannesburg",
- "hostname": "za2-auto-udp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "154.47.30.166",
- "154.47.30.167"
+ "176.125.231.184",
+ "176.125.231.185",
+ "176.125.231.183"
]
},
{
"vpn": "openvpn",
- "country": "Spain",
- "region": "Madrid",
- "city": "Madrid",
- "hostname": "es2-auto-udp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "45.134.213.7"
+ "146.70.155.2",
+ "38.54.76.28",
+ "212.3.233.134",
+ "38.60.202.210"
]
},
{
"vpn": "openvpn",
- "country": "Sweden",
- "region": "Stockholm",
- "city": "Stockholm",
- "hostname": "se2-auto-tcp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-tcp-pf.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "port_forward": true,
"ips": [
- "169.150.208.164"
+ "146.70.155.2",
+ "38.54.76.28",
+ "212.3.233.134",
+ "38.60.202.210"
]
},
{
"vpn": "openvpn",
- "country": "Sweden",
- "region": "Stockholm",
- "city": "Stockholm",
- "hostname": "se2-auto-udp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "169.150.208.135",
- "169.150.208.137"
+ "176.125.231.186",
+ "176.125.231.187",
+ "176.125.231.188"
]
},
{
"vpn": "openvpn",
- "country": "Switzerland",
- "region": "Zurich",
- "city": "Zürich",
- "hostname": "ch2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-udp-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
"ips": [
- "149.102.238.133",
- "149.102.238.135"
+ "146.70.155.2",
+ "38.54.76.28",
+ "212.3.233.134",
+ "38.60.202.210"
]
},
{
"vpn": "openvpn",
- "country": "Switzerland",
- "region": "Zurich",
- "city": "Zürich",
- "hostname": "ch2-auto-udp.ptoserver.com",
+ "country": "United Arab Emirates",
+ "categories": [
+ "p2p"
+ ],
+ "hostname": "ae2-udp-qr-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "149.102.238.133",
- "149.102.238.134",
- "149.102.238.135"
+ "176.125.231.184",
+ "176.125.231.185",
+ "176.125.231.183"
]
},
{
"vpn": "openvpn",
- "country": "Taiwan",
- "region": "Taiwan",
- "city": "Taipei",
- "hostname": "tw2-auto-tcp.ptoserver.com",
+ "country": "United Kingdom",
+ "hostname": "uk2-auto-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "103.59.108.214",
- "103.59.109.55",
- "103.59.109.56"
+ "154.47.24.244",
+ "134.0.220.225",
+ "45.141.154.42",
+ "82.178.32.238",
+ "5.254.2.210",
+ "212.103.51.182",
+ "62.115.44.254",
+ "5.254.112.98",
+ "62.233.127.182",
+ "83.170.114.139",
+ "77.243.185.217",
+ "5.254.106.30",
+ "77.243.185.67",
+ "195.66.225.162",
+ "94.154.158.20",
+ "5.254.58.98",
+ "103.214.44.4",
+ "212.73.253.101"
]
},
{
"vpn": "openvpn",
- "country": "Taiwan",
- "region": "Taiwan",
- "city": "Taipei",
- "hostname": "tw2-auto-udp.ptoserver.com",
+ "country": "United Kingdom",
+ "hostname": "uk2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "103.59.108.214",
- "103.59.109.21",
- "103.59.109.56"
+ "5.254.106.10",
+ "5.254.106.11",
+ "5.254.106.12",
+ "5.254.106.13",
+ "5.254.112.107",
+ "89.238.130.226",
+ "138.199.31.196",
+ "5.254.112.106",
+ "84.233.251.9",
+ "84.233.251.10",
+ "146.70.181.240"
]
},
{
"vpn": "openvpn",
- "country": "Turkey",
- "region": "Istanbul",
- "city": "Istanbul",
- "hostname": "tr2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "country": "United Kingdom",
+ "hostname": "uk2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.136.155.5",
- "45.136.155.6"
+ "5.254.112.103",
+ "5.254.112.105",
+ "84.233.251.8",
+ "91.90.121.153",
+ "146.70.181.232",
+ "5.254.106.6",
+ "5.254.106.8",
+ "91.90.121.152"
]
},
{
"vpn": "openvpn",
- "country": "Turkey",
- "region": "Istanbul",
- "city": "Istanbul",
- "hostname": "tr2-auto-udp.ptoserver.com",
+ "country": "United Kingdom",
+ "hostname": "uk2-auto-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
"ips": [
- "45.136.155.5",
- "45.136.155.6",
- "45.136.155.7",
- "45.136.155.10",
- "45.136.155.12"
+ "154.47.24.244",
+ "134.0.220.225",
+ "45.141.154.42",
+ "82.178.32.238",
+ "5.254.2.210",
+ "212.103.51.182",
+ "62.115.44.254",
+ "5.254.112.98",
+ "62.233.127.182",
+ "83.170.114.139",
+ "77.243.185.217",
+ "5.254.106.30",
+ "77.243.185.67",
+ "195.66.225.162",
+ "94.154.158.20",
+ "5.254.58.98",
+ "103.214.44.4",
+ "212.73.253.101"
]
},
{
"vpn": "openvpn",
- "country": "United Arab Emirates",
- "region": "Dubai",
- "city": "Dubai",
- "hostname": "ae2-auto-tcp.ptoserver.com",
+ "country": "United Kingdom",
+ "hostname": "uk2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "154.47.24.244",
+ "134.0.220.225",
+ "45.141.154.42",
+ "82.178.32.238",
+ "5.254.2.210",
+ "212.103.51.182",
+ "62.115.44.254",
+ "5.254.112.98",
+ "62.233.127.182",
+ "83.170.114.139",
+ "77.243.185.217",
+ "5.254.106.30",
+ "77.243.185.67",
+ "195.66.225.162",
+ "94.154.158.20",
+ "5.254.58.98",
+ "103.214.44.4",
+ "212.73.253.101"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "hostname": "uk2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "146.70.155.8"
+ "149.40.48.44",
+ "149.40.48.52"
]
},
{
"vpn": "openvpn",
- "country": "United Arab Emirates",
- "region": "Dubai",
- "city": "Dubai",
- "hostname": "ae2-auto-udp.ptoserver.com",
+ "country": "United Kingdom",
+ "hostname": "uk2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "146.70.155.7",
- "146.70.155.10",
- "146.70.155.11"
+ "154.47.24.244",
+ "134.0.220.225",
+ "45.141.154.42",
+ "82.178.32.238",
+ "5.254.2.210",
+ "212.103.51.182",
+ "62.115.44.254",
+ "5.254.112.98",
+ "62.233.127.182",
+ "83.170.114.139",
+ "77.243.185.217",
+ "5.254.106.30",
+ "77.243.185.67",
+ "195.66.225.162",
+ "94.154.158.20",
+ "5.254.58.98",
+ "103.214.44.4",
+ "212.73.253.101"
]
},
{
"vpn": "openvpn",
"country": "United Kingdom",
- "region": "England",
"city": "London",
- "hostname": "uk2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "hostname": "ukl2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "5.254.112.102"
+ "5.254.106.11",
+ "5.254.106.12",
+ "5.254.112.107",
+ "5.254.106.13",
+ "138.199.31.196",
+ "5.254.112.106"
]
},
{
"vpn": "openvpn",
"country": "United Kingdom",
- "region": "England",
"city": "London",
- "hostname": "ukl2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "hostname": "ukl2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "5.254.106.5",
- "5.254.112.69",
+ "5.254.106.6",
+ "5.254.106.8",
+ "5.254.106.7",
"5.254.112.103",
- "138.199.31.4",
- "138.199.31.14"
+ "138.199.31.193",
+ "5.254.112.104"
]
},
{
"vpn": "openvpn",
"country": "United Kingdom",
- "region": "England",
"city": "London",
- "hostname": "ukl2-auto-udp.ptoserver.com",
+ "hostname": "ukl2-obf-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "5.254.106.6",
- "5.254.112.103",
- "138.199.31.14"
+ "5.254.106.12",
+ "5.254.106.15",
+ "5.254.106.17",
+ "5.254.106.16",
+ "5.254.106.14"
]
},
{
"vpn": "openvpn",
"country": "United Kingdom",
- "region": "England",
- "city": "Manchester",
- "hostname": "uk2-auto-udp.ptoserver.com",
+ "city": "London",
+ "hostname": "ukl2-udp-qr-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "91.90.121.151",
- "91.90.121.152"
+ "149.40.48.44",
+ "149.40.48.52"
]
},
{
"vpn": "openvpn",
"country": "United Kingdom",
- "region": "England",
"city": "Manchester",
- "hostname": "ukm2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "hostname": "ukm2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "91.90.121.151",
- "91.90.121.152"
+ "84.233.251.9",
+ "146.70.181.240",
+ "146.70.181.241",
+ "89.238.130.227",
+ "89.238.130.226",
+ "84.233.251.10",
+ "146.70.181.239"
]
},
{
"vpn": "openvpn",
"country": "United Kingdom",
- "region": "England",
"city": "Manchester",
- "hostname": "ukm2-auto-udp.ptoserver.com",
+ "hostname": "ukm2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "91.90.121.151",
- "91.90.121.152"
+ "91.90.121.153",
+ "146.70.181.232",
+ "84.233.251.8",
+ "91.90.121.154",
+ "146.70.181.231",
+ "146.70.181.233"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "California",
- "city": "Los Angeles",
- "hostname": "usca2-auto-tcp.ptoserver.com",
+ "hostname": "us-global-tcp2.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "38.122.46.50",
+ "195.22.206.114",
+ "176.10.83.101",
+ "38.142.233.114",
+ "5.254.77.154",
+ "212.103.51.237",
+ "38.142.33.186",
+ "38.142.86.2",
+ "185.124.240.124",
+ "37.221.174.112",
+ "38.142.16.210",
+ "45.134.142.84",
+ "38.88.50.74",
+ "4.69.141.50",
+ "64.31.13.252",
+ "38.104.85.170",
+ "64.31.13.253",
+ "4.2.94.86",
+ "143.244.47.36",
+ "143.198.161.133",
+ "184.75.221.218",
+ "4.69.219.142",
+ "4.8.5.194",
+ "146.70.41.34",
+ "104.216.247.69",
+ "5.254.81.162",
+ "38.122.64.74",
+ "62.115.120.177",
+ "86.106.87.162",
+ "38.32.13.10",
+ "38.32.69.90",
+ "38.88.195.154",
+ "45.144.112.3",
+ "45.92.192.123",
+ "107.150.158.10",
+ "45.9.15.9",
+ "45.83.22.162",
+ "69.25.117.17",
+ "69.25.116.17",
+ "74.217.183.17",
+ "154.24.82.86"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "hostname": "us2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "38.122.46.50",
+ "195.22.206.114",
+ "176.10.83.101",
+ "38.142.233.114",
+ "5.254.77.154",
+ "212.103.51.237",
+ "38.142.33.186",
+ "38.142.86.2",
+ "185.124.240.124",
+ "37.221.174.112",
+ "38.142.16.210",
+ "45.134.142.84",
+ "38.88.50.74",
+ "4.69.141.50",
+ "64.31.13.252",
+ "38.104.85.170",
+ "64.31.13.253",
+ "4.2.94.86",
+ "143.244.47.36",
+ "143.198.161.133",
+ "184.75.221.218",
+ "4.69.219.142",
+ "4.8.5.194",
+ "146.70.41.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "hostname": "us2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "64.31.3.107",
+ "64.31.20.12",
+ "169.150.205.138",
+ "217.148.140.140",
+ "217.148.140.141",
+ "45.134.142.72",
+ "64.31.3.108",
+ "64.31.20.14",
+ "107.175.196.108",
+ "146.70.212.76",
+ "149.102.243.12",
+ "192.3.53.233",
+ "107.175.196.107",
+ "45.134.142.73",
+ "64.31.20.15",
+ "149.40.49.202",
+ "217.148.140.139"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "hostname": "us2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "146.70.218.71",
- "146.70.218.72"
+ "45.134.142.69",
+ "107.175.196.105",
+ "146.70.212.73",
+ "149.88.18.8",
+ "149.88.25.200",
+ "149.88.25.211",
+ "217.148.140.135",
+ "45.134.142.70",
+ "64.31.3.104",
+ "138.199.43.7",
+ "149.88.25.198",
+ "149.102.243.7",
+ "217.148.140.137",
+ "107.175.196.106",
+ "149.40.49.200",
+ "45.134.142.89",
+ "64.31.20.9",
+ "138.199.43.9",
+ "149.88.18.9",
+ "149.102.243.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "hostname": "us2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021,
+ 1210
+ ],
+ "ips": [
+ "38.122.46.50",
+ "195.22.206.114",
+ "176.10.83.101",
+ "38.142.233.114",
+ "5.254.77.154",
+ "212.103.51.237",
+ "38.142.33.186",
+ "38.142.86.2",
+ "185.124.240.124",
+ "37.221.174.112",
+ "38.142.16.210",
+ "45.134.142.84",
+ "38.88.50.74",
+ "4.69.141.50",
+ "64.31.13.252",
+ "38.104.85.170",
+ "64.31.13.253",
+ "4.2.94.86",
+ "143.244.47.36",
+ "143.198.161.133",
+ "184.75.221.218",
+ "4.69.219.142",
+ "4.8.5.194",
+ "146.70.41.34",
+ "104.216.247.69",
+ "5.254.81.162",
+ "38.122.64.74",
+ "62.115.120.177",
+ "86.106.87.162",
+ "38.32.13.10",
+ "38.32.69.90",
+ "38.88.195.154",
+ "45.144.112.3",
+ "45.92.192.123",
+ "107.150.158.10",
+ "45.9.15.9",
+ "45.83.22.162",
+ "69.25.117.17",
+ "69.25.116.17",
+ "74.217.183.17",
+ "154.24.82.86"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "California",
- "city": "Los Angeles",
- "hostname": "usca2-auto-udp.ptoserver.com",
+ "hostname": "us2-obf-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "138.199.35.38",
- "138.199.35.39"
+ "138.199.43.14",
+ "146.70.212.75",
+ "146.70.212.76",
+ "192.3.53.235",
+ "45.134.140.235",
+ "79.127.197.75",
+ "107.175.196.108",
+ "149.88.25.207",
+ "149.102.243.10",
+ "217.148.140.140",
+ "45.134.142.73",
+ "143.244.44.146"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "California",
- "city": "Los Angeles",
- "hostname": "usphx2-auto-tcp.ptoserver.com",
+ "hostname": "us2-tcp.ptoserver.com",
"tcp": true,
+ "tcp_ports": [
+ 80
+ ],
"ips": [
- "217.148.140.135"
+ "104.216.247.69",
+ "37.221.174.112",
+ "5.254.81.162",
+ "38.122.64.74",
+ "146.70.41.34",
+ "62.115.120.177",
+ "176.10.83.101",
+ "86.106.87.162",
+ "5.254.77.154",
+ "143.198.161.133",
+ "38.142.233.114",
+ "38.32.13.10",
+ "212.103.51.237",
+ "38.32.69.90",
+ "38.142.16.210",
+ "38.122.46.50",
+ "45.134.142.84",
+ "38.88.195.154",
+ "38.142.86.2",
+ "38.142.33.186",
+ "45.144.112.3",
+ "38.88.50.74",
+ "45.92.192.123",
+ "107.150.158.10",
+ "45.9.15.9",
+ "45.83.22.162",
+ "69.25.117.17",
+ "69.25.116.17",
+ "74.217.183.17",
+ "154.24.82.86"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "California",
- "city": "Los Angeles",
- "hostname": "usphx2-auto-udp.ptoserver.com",
+ "hostname": "us2-udp-obf-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 5565
+ ],
+ "port_forward": true,
+ "obfuscated": true,
"ips": [
- "217.148.140.134",
- "217.148.140.136"
+ "149.40.51.41",
+ "149.40.51.42"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "California",
- "city": "San Jose",
- "hostname": "ussf2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "hostname": "us2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "93.115.200.86",
- "93.115.200.87"
+ "143.244.47.48",
+ "149.40.51.40",
+ "149.40.51.48",
+ "217.138.217.69"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "California",
- "city": "San Jose",
- "hostname": "ussf2-auto-udp.ptoserver.com",
+ "hostname": "us2-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
"ips": [
- "93.115.200.86"
+ "45.134.142.71",
+ "64.31.20.10",
+ "146.70.212.74",
+ "149.88.25.198",
+ "185.220.69.104",
+ "45.134.140.231",
+ "64.31.20.9",
+ "149.40.49.211",
+ "149.40.49.212",
+ "149.88.18.9",
+ "149.102.243.7",
+ "64.31.3.105",
+ "149.88.18.8",
+ "149.102.243.8"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Florida",
- "city": "Miami",
- "hostname": "usfl2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Ashburn",
+ "hostname": "usva2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "45.134.142.68",
- "146.70.228.87"
+ "149.88.18.14",
+ "149.88.18.15"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Florida",
- "city": "Miami",
- "hostname": "usfl2-auto-udp.ptoserver.com",
+ "city": "Ashburn",
+ "hostname": "usva2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.134.142.69",
- "146.70.228.87"
+ "149.88.18.8",
+ "149.88.18.7",
+ "149.88.18.9"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Georgia",
"city": "Atlanta",
- "hostname": "us-global-tcp2.ptoserver.com",
- "tcp": true,
+ "hostname": "usga2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "45.134.140.230"
+ "45.134.140.234",
+ "45.134.140.235"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Georgia",
"city": "Atlanta",
- "hostname": "us-global-udp2.ptoserver.com",
+ "hostname": "usga2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.134.140.231"
+ "45.134.140.231",
+ "45.134.140.232",
+ "45.134.140.246"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Georgia",
- "city": "Atlanta",
- "hostname": "usga2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Chicago",
+ "hostname": "usil2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "45.134.140.230"
+ "149.88.25.205",
+ "149.88.25.206",
+ "149.88.25.207"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Georgia",
- "city": "Atlanta",
- "hostname": "usga2-auto-udp.ptoserver.com",
+ "city": "Chicago",
+ "hostname": "usil2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "45.134.140.230"
+ "149.88.25.198",
+ "149.88.25.211",
+ "149.88.25.200",
+ "149.88.25.212"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Illinois",
- "city": "Chicago",
- "hostname": "usil2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Houston",
+ "hostname": "ustx2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "87.249.134.174",
- "149.88.25.196",
- "149.88.25.211"
+ "79.127.197.76",
+ "169.150.205.137",
+ "169.150.205.138",
+ "169.150.205.139",
+ "169.150.205.140",
+ "79.127.197.74",
+ "79.127.197.75"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Illinois",
- "city": "Chicago",
- "hostname": "usil2-auto-udp.ptoserver.com",
+ "city": "Houston",
+ "hostname": "ustx2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "87.249.134.175",
- "149.88.25.196"
+ "79.127.197.71",
+ "79.127.197.72",
+ "169.150.205.134",
+ "79.127.197.73",
+ "169.150.205.135",
+ "169.150.205.136",
+ "169.150.205.147"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New Jersey",
- "city": "Secaucus",
- "hostname": "usnj2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Los Angeles",
+ "hostname": "usca2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "146.70.212.71",
- "146.70.212.72"
+ "149.102.243.8",
+ "149.102.243.9",
+ "149.102.243.20",
+ "149.102.243.7"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New Jersey",
- "city": "Secaucus",
- "hostname": "usnj2-auto-udp.ptoserver.com",
+ "city": "Los Angeles",
+ "hostname": "usca2-obf-udp.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "146.70.212.71",
- "146.70.212.72"
+ "217.138.217.72",
+ "217.138.217.73",
+ "217.138.217.76",
+ "217.138.217.75"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New Jersey",
- "city": "Secaucus",
- "hostname": "usny2-auto-udp.ptoserver.com",
+ "city": "Los Angeles",
+ "hostname": "usca2-udp-qr-pf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "146.70.215.22",
- "149.40.49.133"
+ "217.138.217.69",
+ "217.138.217.70"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New Jersey",
- "city": "Weehawken",
- "hostname": "usny2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Miami",
+ "hostname": "usfl2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "143.244.44.133",
- "149.40.49.133",
- "149.40.49.197"
+ "45.134.142.72",
+ "45.134.142.73"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New York",
- "city": "New York City",
- "hostname": "ar2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Miami",
+ "hostname": "usfl2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.111.152.6"
+ "45.134.142.70",
+ "45.134.142.69",
+ "45.134.142.89",
+ "45.134.142.71"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New York",
- "city": "New York City",
- "hostname": "ar2-auto-udp.ptoserver.com",
+ "city": "New York",
+ "hostname": "usny2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.152.6"
+ "38.132.102.11",
+ "38.132.102.12",
+ "149.40.49.201",
+ "149.40.49.202",
+ "143.244.44.146",
+ "185.220.69.107"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New York",
- "city": "New York City",
- "hostname": "aw2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "New York",
+ "hostname": "usny2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "172.111.183.4"
+ "38.132.102.8",
+ "143.244.44.135",
+ "149.40.49.199",
+ "185.220.69.104",
+ "38.132.102.9",
+ "149.40.49.200",
+ "185.220.69.105",
+ "185.220.69.106",
+ "37.120.138.10",
+ "38.132.102.7",
+ "149.40.49.211"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New York",
- "city": "New York City",
- "hostname": "aw2-auto-udp.ptoserver.com",
+ "city": "New York",
+ "hostname": "usny2-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.183.4"
+ "143.244.47.51",
+ "143.244.47.49",
+ "143.244.47.50",
+ "143.244.47.52"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New York",
- "city": "New York City",
- "hostname": "bo2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "New York",
+ "hostname": "usny2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "172.111.241.133",
- "172.111.241.134"
+ "192.253.246.246"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "New York",
- "city": "New York City",
- "hostname": "bo2-auto-udp.ptoserver.com",
+ "city": "Newark",
+ "hostname": "usnj2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "172.111.241.4",
- "172.111.241.6"
+ "146.70.212.76",
+ "146.70.212.78",
+ "146.70.212.77",
+ "146.70.212.75"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Texas",
- "city": "Houston",
- "hostname": "ustx2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Newark",
+ "hostname": "usnj2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "149.40.58.197",
- "149.40.58.198"
+ "146.70.212.73",
+ "146.70.212.74",
+ "146.70.212.72"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Texas",
- "city": "Houston",
- "hostname": "ustx2-auto-udp.ptoserver.com",
+ "city": "Phoenix",
+ "hostname": "usphx2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "149.40.58.197",
- "149.40.58.198"
+ "217.148.140.139",
+ "217.148.140.140",
+ "217.148.140.141",
+ "217.148.140.138"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Utah",
- "city": "Riverton",
- "hostname": "usut2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Phoenix",
+ "hostname": "usphx2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "67.213.219.216"
+ "217.148.140.135",
+ "217.148.140.136",
+ "217.148.140.137"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Utah",
- "city": "Riverton",
- "hostname": "usut2-auto-udp.ptoserver.com",
+ "city": "Salt Lake City",
+ "hostname": "usut2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "67.213.219.186",
- "67.213.219.190"
+ "64.31.3.106",
+ "64.31.3.108",
+ "64.31.3.107"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Virginia",
- "city": "Ashburn",
- "hostname": "usva2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "San Francisco",
+ "hostname": "ussf2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "5.254.108.198"
+ "107.175.196.108",
+ "107.175.196.109",
+ "107.175.196.110",
+ "107.175.196.107"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Virginia",
- "city": "Ashburn",
- "hostname": "usva2-auto-udp.ptoserver.com",
+ "city": "San Francisco",
+ "hostname": "ussf2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "5.254.108.197",
- "5.254.108.199"
+ "107.175.196.105",
+ "107.175.196.106"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Virginia",
- "city": "Herndon",
- "hostname": "uswdc2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "city": "Seattle",
+ "hostname": "ussa2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 1210
+ ],
+ "obfuscated": true,
"ips": [
- "5.254.43.229",
- "5.254.43.230"
+ "138.199.43.13",
+ "192.3.53.235",
+ "138.199.43.10",
+ "138.199.43.11",
+ "138.199.43.12",
+ "192.3.53.232",
+ "192.3.53.234"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Virginia",
- "city": "Herndon",
- "hostname": "uswdc2-auto-udp.ptoserver.com",
+ "city": "Seattle",
+ "hostname": "ussa2-auto-udp-qr.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
"ips": [
- "5.254.40.39",
- "5.254.43.230",
- "5.254.43.231",
- "5.254.43.232"
+ "192.3.53.231",
+ "138.199.43.8",
+ "156.146.51.163",
+ "192.3.53.230",
+ "138.199.43.7"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Washington",
"city": "Seattle",
- "hostname": "ussa2-auto-tcp.ptoserver.com",
- "tcp": true,
+ "hostname": "ussa2-udp-qr-pf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "port_forward": true,
+ "quantum_resistant": true,
"ips": [
- "138.199.43.6",
- "138.199.43.23",
- "138.199.43.24"
+ "149.40.51.40",
+ "149.40.51.48"
]
},
{
"vpn": "openvpn",
"country": "United States",
- "region": "Washington",
- "city": "Seattle",
- "hostname": "ussa2-auto-udp.ptoserver.com",
+ "city": "Washington DC",
+ "hostname": "uswdc2-auto-udp-obf.ptoserver.com",
"udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "obfuscated": true,
"ips": [
- "138.199.43.6",
- "138.199.43.7",
- "138.199.43.23"
+ "64.31.20.13",
+ "64.31.20.11",
+ "64.31.20.12",
+ "64.31.20.15"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Vietnam",
+ "hostname": "vn2-auto-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "222.255.100.3",
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Vietnam",
+ "hostname": "vn2-auto-udp-obf.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 5565,
+ 1210
+ ],
+ "obfuscated": true,
+ "ips": [
+ "172.111.197.8",
+ "172.111.197.7"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Vietnam",
+ "hostname": "vn2-auto-udp-qr.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "quantum_resistant": true,
+ "ips": [
+ "172.111.197.6",
+ "172.111.197.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Vietnam",
+ "hostname": "vn2-auto-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "222.255.100.3",
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Vietnam",
+ "hostname": "vn2-tcp.ptoserver.com",
+ "tcp": true,
+ "tcp_ports": [
+ 80
+ ],
+ "ips": [
+ "222.255.100.3",
+ "2.58.44.162"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Vietnam",
+ "hostname": "vn2-udp.ptoserver.com",
+ "udp": true,
+ "udp_ports": [
+ 15021
+ ],
+ "ips": [
+ "222.255.100.3",
+ "2.58.44.162"
]
}
]