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" ] } ]