diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 52b71c5e..3d01c7d0 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -54,6 +54,7 @@ body:
- PrivateVPN
- ProtonVPN
- PureVPN
+ - SlickVPN
- Surfshark
- TorGuard
- VPNUnlimited
diff --git a/.github/labels.yml b/.github/labels.yml
index 5b2cddc6..a142c5e5 100644
--- a/.github/labels.yml
+++ b/.github/labels.yml
@@ -64,6 +64,9 @@
- name: ":cloud: PureVPN"
color: "cfe8d4"
description: ""
+- name: ":cloud: SlickVPN"
+ color: "cfe8d4"
+ description: ""
- name: ":cloud: Surfshark"
color: "cfe8d4"
description: ""
diff --git a/go.mod b/go.mod
index 936f3333..c7950843 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.0
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
+ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/text v0.3.7
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
@@ -40,6 +41,5 @@ require (
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
- golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 70694730..d66aa12b 100644
--- a/go.sum
+++ b/go.sum
@@ -179,8 +179,9 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
diff --git a/internal/configuration/settings/openvpnselection.go b/internal/configuration/settings/openvpnselection.go
index e62e5ebe..8e1f64be 100644
--- a/internal/configuration/settings/openvpnselection.go
+++ b/internal/configuration/settings/openvpnselection.go
@@ -82,6 +82,9 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
case providers.Protonvpn:
allowedTCP = []uint16{443, 5995, 8443}
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
+ case providers.SlickVPN:
+ allowedTCP = []uint16{443, 8080, 8888}
+ allowedUDP = []uint16{443, 8080, 8888}
case providers.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198}
diff --git a/internal/constants/providers/providers.go b/internal/constants/providers/providers.go
index 3855fdc2..528dfe5b 100644
--- a/internal/constants/providers/providers.go
+++ b/internal/constants/providers/providers.go
@@ -19,6 +19,7 @@ const (
Privatevpn = "privatevpn"
Protonvpn = "protonvpn"
Purevpn = "purevpn"
+ SlickVPN = "slickvpn"
Surfshark = "surfshark"
Torguard = "torguard"
VPNUnlimited = "vpn unlimited"
@@ -44,6 +45,7 @@ func All() []string {
Privatevpn,
Protonvpn,
Purevpn,
+ SlickVPN,
Surfshark,
Torguard,
VPNUnlimited,
diff --git a/internal/models/markdown.go b/internal/models/markdown.go
index 3b305153..f0482559 100644
--- a/internal/models/markdown.go
+++ b/internal/models/markdown.go
@@ -123,6 +123,8 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, freeHeader}
case providers.Purevpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
+ case providers.SlickVPN:
+ return []string{regionHeader, countryHeader, cityHeader, hostnameHeader}
case providers.Surfshark:
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
case providers.Torguard:
diff --git a/internal/provider/hidemyass/updater/hosttourl.go b/internal/provider/hidemyass/updater/hosttourl.go
index 0588f41f..486234e8 100644
--- a/internal/provider/hidemyass/updater/hosttourl.go
+++ b/internal/provider/hidemyass/updater/hosttourl.go
@@ -33,5 +33,11 @@ func getHostToURL(ctx context.Context, client *http.Client, protocol string) (
return nil, err
}
- return openvpn.FetchMultiFiles(ctx, client, urls)
+ const failEarly = true
+ hostToURL, errors := openvpn.FetchMultiFiles(ctx, client, urls, failEarly)
+ if len(errors) > 0 {
+ return nil, errors[0]
+ }
+
+ return hostToURL, nil
}
diff --git a/internal/provider/providers.go b/internal/provider/providers.go
index bd0ef286..6d448884 100644
--- a/internal/provider/providers.go
+++ b/internal/provider/providers.go
@@ -25,6 +25,7 @@ import (
"github.com/qdm12/gluetun/internal/provider/privatevpn"
"github.com/qdm12/gluetun/internal/provider/protonvpn"
"github.com/qdm12/gluetun/internal/provider/purevpn"
+ "github.com/qdm12/gluetun/internal/provider/slickvpn"
"github.com/qdm12/gluetun/internal/provider/surfshark"
"github.com/qdm12/gluetun/internal/provider/torguard"
"github.com/qdm12/gluetun/internal/provider/vpnunlimited"
@@ -71,6 +72,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
providers.Privatevpn: privatevpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
providers.Protonvpn: protonvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
providers.Purevpn: purevpn.New(storage, randSource, 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),
providers.VPNUnlimited: vpnunlimited.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
diff --git a/internal/provider/slickvpn/connection.go b/internal/provider/slickvpn/connection.go
new file mode 100644
index 00000000..4aee167e
--- /dev/null
+++ b/internal/provider/slickvpn/connection.go
@@ -0,0 +1,13 @@
+package slickvpn
+
+import (
+ "github.com/qdm12/gluetun/internal/configuration/settings"
+ "github.com/qdm12/gluetun/internal/models"
+ "github.com/qdm12/gluetun/internal/provider/utils"
+)
+
+func (p *Provider) GetConnection(selection settings.ServerSelection) (
+ connection models.Connection, err error) {
+ defaults := utils.NewConnectionDefaults(0, 443, 0) //nolint:gomnd
+ return utils.GetConnection(p.Name(), p.storage, selection, defaults, p.randSource)
+}
diff --git a/internal/provider/slickvpn/openvpnconf.go b/internal/provider/slickvpn/openvpnconf.go
new file mode 100644
index 00000000..132c22eb
--- /dev/null
+++ b/internal/provider/slickvpn/openvpnconf.go
@@ -0,0 +1,30 @@
+package slickvpn
+
+import (
+ "github.com/qdm12/gluetun/internal/configuration/settings"
+ "github.com/qdm12/gluetun/internal/constants/openvpn"
+ "github.com/qdm12/gluetun/internal/models"
+ "github.com/qdm12/gluetun/internal/provider/utils"
+)
+
+func (p *Provider) OpenVPNConfig(connection models.Connection,
+ settings settings.OpenVPN) (lines []string) {
+ const pingSeconds = 10
+ const bufSize = 393216
+ providerSettings := utils.OpenVPNProviderSettings{
+ RemoteCertTLS: true,
+ AuthUserPass: true,
+ Ciphers: []string{
+ openvpn.AES256cbc,
+ },
+ Ping: pingSeconds,
+ SndBuf: bufSize,
+ RcvBuf: bufSize,
+ // Certificate found from https://www.slickvpn.com/tutorials/using-openvpn-configuration-files/
+ CA: "MIIESDCCAzCgAwIBAgIJAKHK5bbBPSU2MA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNWUE4xDDAKBgNVBAcTA1ZQTjEMMAoGA1UEChMDVlBOMQwwCgYDVQQLEwNWUE4xDDAKBgNVBAMTA1ZQTjEMMAoGA1UEKRMDVlBOMRIwEAYJKoZIhvcNAQkBFgNWUE4wHhcNMjIwMjE0MjEzNDQwWhcNMzIwMjEyMjEzNDQwWjB1MQswCQYDVQQGEwJVUzEMMAoGA1UECBMDVlBOMQwwCgYDVQQHEwNWUE4xDDAKBgNVBAoTA1ZQTjEMMAoGA1UECxMDVlBOMQwwCgYDVQQDEwNWUE4xDDAKBgNVBCkTA1ZQTjESMBAGCSqGSIb3DQEJARYDVlBOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUl1XkfGo3c1uFsvgbO3C3yvu0+cHs9IUSsju5U9cPNCo53mqRHU/qntCC+ldIDKN+dNWn7eURIDszy+flutkgucs0qgETy5fzpXnVMtiKmMiOYWiJDor7j7QivRaxoT/O2fyjxvVCL8gMa60ekWSGBT6isYY8t8BnwTPVP0KvDm36wdaqLmubhf2XGvka/hhNx0SXMmz2x3OJ8BcoypZVLLk/+Qm6DJh1KxyDi4kI+jBC41QuaKKDNwb0kth1304eqZoUeCXtGkzl91y76ODAfdqzXf9WYJdgkXpOm53Cg7FtB42AqPRqHJVwYxDnQyrFwy4a3CWqFJnKtxJM/WlwIDAQABo4HaMIHXMB0GA1UdDgQWBBRSzxAtISfbSRPr0fmhwNZc8kOeKzCBpwYDVR0jBIGfMIGcgBRSzxAtISfbSRPr0fmhwNZc8kOeK6F5pHcwdTELMAkGA1UEBhMCVVMxDDAKBgNVBAgTA1ZQTjEMMAoGA1UEBxMDVlBOMQwwCgYDVQQKEwNWUE4xDDAKBgNVBAsTA1ZQTjEMMAoGA1UEAxMDVlBOMQwwCgYDVQQpEwNWUE4xEjAQBgkqhkiG9w0BCQEWA1ZQToIJAKHK5bbBPSU2MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGuKFW765F3D5wax5IFSQbEtr+rVHgjR8jiYTzxOCmbLaU4oj6phOhfQJiTTADQYgCIC/DN0HsAEEqrKkwEn8KdAoNiAWfqCV/eqnK83y7yRDGx6/zfsch+PAzKZouMJLrvR9RYbHq7m3adZv84YLge7FE1JqFk1j6rSa4dUUnGQPrQgr9Sasnz8O8KK45XH6fqKrsd4p485n+BXPDzWVsHl4M5dqQV7qUZTazbzzh4NyP5/RQ6Oh5jqMN7po4qiqWv1pu0EKDxUG6gcECc2cTQwHhIOPeCGdHS7uuI2FlLnHaCUFBgi8zTsZxaeaPuPch5M7Zxbdg0GBhS2SmNi+io=", //nolint:lll
+ ExtraLines: []string{
+ "redirect-gateway",
+ },
+ }
+ return utils.OpenVPNConfig(providerSettings, connection, settings)
+}
diff --git a/internal/provider/slickvpn/provider.go b/internal/provider/slickvpn/provider.go
new file mode 100644
index 00000000..c3b02174
--- /dev/null
+++ b/internal/provider/slickvpn/provider.go
@@ -0,0 +1,33 @@
+package slickvpn
+
+import (
+ "math/rand"
+ "net/http"
+
+ "github.com/qdm12/gluetun/internal/constants/providers"
+ "github.com/qdm12/gluetun/internal/provider/common"
+ "github.com/qdm12/gluetun/internal/provider/slickvpn/updater"
+ "github.com/qdm12/gluetun/internal/provider/utils"
+)
+
+type Provider struct {
+ storage common.Storage
+ randSource rand.Source
+ utils.NoPortForwarder
+ common.Fetcher
+}
+
+func New(storage common.Storage, randSource rand.Source,
+ client *http.Client, updaterWarner common.Warner,
+ parallelResolver common.ParallelResolver) *Provider {
+ return &Provider{
+ storage: storage,
+ randSource: randSource,
+ NoPortForwarder: utils.NewNoPortForwarding(providers.SlickVPN),
+ Fetcher: updater.New(client, updaterWarner, parallelResolver),
+ }
+}
+
+func (p *Provider) Name() string {
+ return providers.SlickVPN
+}
diff --git a/internal/provider/slickvpn/updater/helpers_test.go b/internal/provider/slickvpn/updater/helpers_test.go
new file mode 100644
index 00000000..7025a6b0
--- /dev/null
+++ b/internal/provider/slickvpn/updater/helpers_test.go
@@ -0,0 +1,26 @@
+package updater
+
+import (
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "golang.org/x/net/html"
+)
+
+func parseTestHTML(t *testing.T, htmlString string) *html.Node {
+ t.Helper()
+ rootNode, err := html.Parse(strings.NewReader(htmlString))
+ require.NoError(t, err)
+ return rootNode
+}
+
+func parseTestDataIndexHTML(t *testing.T) *html.Node {
+ t.Helper()
+
+ data, err := os.ReadFile("testdata/index.html")
+ require.NoError(t, err)
+
+ return parseTestHTML(t, string(data))
+}
diff --git a/internal/provider/slickvpn/updater/resolve.go b/internal/provider/slickvpn/updater/resolve.go
new file mode 100644
index 00000000..d0b3fa0c
--- /dev/null
+++ b/internal/provider/slickvpn/updater/resolve.go
@@ -0,0 +1,28 @@
+package updater
+
+import (
+ "time"
+
+ "github.com/qdm12/gluetun/internal/updater/resolver"
+)
+
+func parallelResolverSettings(hosts []string) (settings resolver.ParallelSettings) {
+ const (
+ maxFailRatio = 0.1
+ maxDuration = 20 * time.Second
+ betweenDuration = time.Second
+ maxNoNew = 2
+ maxFails = 2
+ )
+ return resolver.ParallelSettings{
+ Hosts: hosts,
+ MaxFailRatio: maxFailRatio,
+ Repeat: resolver.RepeatSettings{
+ MaxDuration: maxDuration,
+ BetweenDuration: betweenDuration,
+ MaxNoNew: maxNoNew,
+ MaxFails: maxFails,
+ SortIPs: true,
+ },
+ }
+}
diff --git a/internal/provider/slickvpn/updater/servers.go b/internal/provider/slickvpn/updater/servers.go
new file mode 100644
index 00000000..e4b78ea7
--- /dev/null
+++ b/internal/provider/slickvpn/updater/servers.go
@@ -0,0 +1,82 @@
+package updater
+
+import (
+ "context"
+ "fmt"
+ "sort"
+
+ "github.com/qdm12/gluetun/internal/constants/vpn"
+ "github.com/qdm12/gluetun/internal/models"
+ "github.com/qdm12/gluetun/internal/provider/common"
+ "github.com/qdm12/gluetun/internal/updater/openvpn"
+)
+
+func (u *Updater) FetchServers(ctx context.Context, minServers int) (
+ servers []models.Server, err error) {
+ hostToData, err := fetchServers(ctx, u.client)
+ if err != nil {
+ return nil, fmt.Errorf("fetching and parsing website: %w", err)
+ }
+
+ openvpnURLs := make([]string, 0, len(hostToData))
+ for _, data := range hostToData {
+ openvpnURLs = append(openvpnURLs, data.ovpnURL)
+ }
+
+ if len(openvpnURLs) < minServers {
+ return nil, fmt.Errorf("%w: %d and expected at least %d",
+ common.ErrNotEnoughServers, len(openvpnURLs), minServers)
+ }
+
+ const failEarly = false // some URLs from the website are not valid
+ udpHostToURL, errors := openvpn.FetchMultiFiles(ctx, u.client, openvpnURLs, failEarly)
+ for _, err := range errors {
+ u.warner.Warn(fmt.Sprintf("fetching OpenVPN files: %s", err))
+ }
+
+ if len(udpHostToURL) < minServers {
+ return nil, fmt.Errorf("%w: %d and expected at least %d",
+ common.ErrNotEnoughServers, len(udpHostToURL), minServers)
+ }
+
+ hosts := make([]string, 0, len(udpHostToURL))
+ for host := range udpHostToURL {
+ hosts = append(hosts, host)
+ }
+
+ resolveSettings := parallelResolverSettings(hosts)
+ hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
+ for _, warning := range warnings {
+ u.warner.Warn(warning)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("resolving hosts: %w", err)
+ }
+
+ if len(hostToIPs) < minServers {
+ return nil, fmt.Errorf("%w: %d and expected at least %d",
+ common.ErrNotEnoughServers, len(hosts), minServers)
+ }
+
+ servers = make([]models.Server, 0, len(hostToIPs))
+ for host, IPs := range hostToIPs {
+ _, udp := udpHostToURL[host]
+
+ serverData := hostToData[host]
+
+ server := models.Server{
+ VPN: vpn.OpenVPN,
+ Region: serverData.region,
+ Country: serverData.country,
+ City: serverData.city,
+ Hostname: host,
+ UDP: udp,
+ IPs: IPs,
+ }
+ servers = append(servers, server)
+ }
+
+ sort.Sort(models.SortableServers(servers))
+
+ return servers, nil
+}
diff --git a/internal/provider/slickvpn/updater/testdata/index.html b/internal/provider/slickvpn/updater/testdata/index.html
new file mode 100644
index 00000000..244a5ba9
--- /dev/null
+++ b/internal/provider/slickvpn/updater/testdata/index.html
@@ -0,0 +1,236 @@
+
SlickVPN Server Locations - SlickVPN SlickVPN Server Locations
Here is a list of all our VPN servers that are currently active. We have 150+ servers in 40+ countries with 250+ anonymous IP addresses.
Vina del Mar, Chile
Server: gw1.kna1.slickvpn.com
2 nodes
Panama City, Panama
Server: gw2.pty1.slickvpn.com
1 nodes
Bangalore, India
Server: gw1.blr1.slickvpn.com
1 nodes
Hong Kong, Hong Kong
Server: gw1.hkg2.slickvpn.com
2 nodes
Rochester, United Kingdom
Server: gw1.rcs1.slickvpn.com
5 nodes
London, United Kingdom
Server: gw3.lhr1.slickvpn.com
1 nodes
Palo Alto, United States
Server: gw2.pao1.slickvpn.com
1 nodes
Newark, United States
Server: gw2.ewr1.slickvpn.com
1 nodes
Atlanta, United States
Server: gw2.atl3.slickvpn.com
1 nodes
Manchester, United Kingdom
Server: gw1.man2.slickvpn.com
1 nodes
Madrid, Spain
Server: gw1.mad1.slickvpn.com
1 nodes
Montreal, Canada
Server: gw2.yul2.slickvpn.com
5 nodes
Los Angeles, United States
Server: gw3.lax3.slickvpn.com
5 nodes
Bursa, Turkey
Server: gw1.yei1.slickvpn.com
1 nodes
Newark, United States
Server: gw3.ewr1.slickvpn.com
1 nodes
Miami, United States
Server: gw2.mia3.slickvpn.com
1 nodes
Amsterdam, Netherlands
Server: gw2.ams3.slickvpn.com
1 nodes
Houston, United States
Server: gw2.hou1.slickvpn.com
1 nodes
Salt Lake City, UT, United States
Server: gw2.slc1.slickvpn.com
1 nodes
Ljubljana, Slovenia
Server: gw1.lju1.slickvpn.com
1 nodes
Singapore, Singapore
Server: gw1.sin2.slickvpn.com
1 nodes
Nürnberg, Germany
Server: gw1.nue1.slickvpn.com
1 nodes
New York, United States
Server: gw1.lga1.slickvpn.com
1 nodes
Thessaloniki, Greece
Server: gw1.skg1.slickvpn.com
1 nodes
Atlanta, United States
Server: gw1.atl3.slickvpn.com
1 nodes
London, United Kingdom
Server: gw1.lhr2.slickvpn.com
1 nodes
Barcelona, Spain
Server: gw2.bcn2.slickvpn.com
1 nodes
Palo Alto, United States
Server: gw1.pao1.slickvpn.com
1 nodes
Tampa, United States
Server: gw1.tpa1.slickvpn.com
1 nodes
Stockholm, Sweden
Server: gw1.arn3.slickvpn.com
2 nodes
Amsterdam, Netherlands
Server: gw1.ams2.slickvpn.com
1 nodes
Zurich, Switzerland
Server: gw1.zrh1.slickvpn.com
1 nodes
Seattle, WA, United States
Server: gw1.sea1.slickvpn.com
1 nodes
Coventry, United Kingdom
Server: gw1.cvt1.slickvpn.com
1 nodes
Dallas, United States
Server: gw2.dfw3.slickvpn.com
1 nodes
St Petersburg, Russian Federation
Server: gw1.led1.slickvpn.com
1 nodes
Amsterdam, Netherlands
Server: gw1.ams4.slickvpn.com
1 nodes
Moscow, Russian Federation
Server: gw1.svo1.slickvpn.com
1 nodes
Dallas, United States
Server: gw3.dfw3.slickvpn.com
1 nodes
Bangkok, Thailand
Server: gw1.bkk1.slickvpn.com
1 nodes
Sydney, Australia
Server: gw2.syd2.slickvpn.com
1 nodes
Dallas, United States
Server: gw1.dfw3.slickvpn.com
1 nodes
Sydney, Australia
Server: gw1.syd1.slickvpn.com
1 nodes
Torp, Norway
Server: gw1.trf1.slickvpn.com
1 nodes
Sao Paulo, Brazil
Server: gw1.gru2.slickvpn.com
1 nodes
Graz, Austria
Server: gw1.grz1.slickvpn.com
2 nodes
Montreal, Canada
Server: gw1.yul2.slickvpn.com
4 nodes
New York, United States
Server: gw1.lga2.slickvpn.com
5 nodes
Budapest, Hungary
Server: gw1.bud1.slickvpn.com
1 nodes
Eastleigh near Southampton, United Kingdom
Server: gw1.sou1.slickvpn.com
1 nodes
Bucharest, Romania
Server: gw1.buh2.slickvpn.com
3 nodes
Jakarta, Indonesia
Server: gw1.cgk1.slickvpn.com
1 nodes
Chicago, United States
Server: gw1.ord3.slickvpn.com
2 nodes
Kuala Lumpur, Malaysia
Server: gw1.kul1.slickvpn.com
1 nodes
Kansas City, United States
Server: gw1.mci2.slickvpn.com
5 nodes
Athens, Greece
Server: gw1.ath1.slickvpn.com
1 nodes
Warsaw, Poland
Server: gw1.waw1.slickvpn.com
2 nodes
Milan, Italy
Server: gw2.mxp2.slickvpn.com
1 nodes
Las Vegas, United States
Server: gw1.las1.slickvpn.com
1 nodes
Ostend, Belgium
Server: gw1.ost2.slickvpn.com
1 nodes
Amsterdam, Netherlands
Server: gw1.ams1.slickvpn.com
2 nodes
Atlanta, United States
Server: gw1.atl1.slickvpn.com
1 nodes
Frankfurt, Germany
Server: gw1.fra2.slickvpn.com
1 nodes
Toronto, Canada
Server: gw2.yyz1.slickvpn.com
1 nodes
Los Angeles, United States
Server: gw1.lax1.slickvpn.com
1 nodes
Chisinau, Moldova
Server: gw1.kiv1.slickvpn.com
1 nodes
Singapore, Singapore
Server: gw1.sin1.slickvpn.com
1 nodes
Ostend, Belgium
Server: gw2.ost2.slickvpn.com
1 nodes
Washington, DC, United States
Server: gw1.iad1.slickvpn.com
5 nodes
Melbourne, Australia
Server: gw2.mel1.slickvpn.com
1 nodes
Morganton, United States
Server: gw1.mrn1.slickvpn.com
1 nodes
Lille, France
Server: gw1.lil1.slickvpn.com
1 nodes
Taipei, Taiwan
Server: gw2.tpe1.slickvpn.com
1 nodes
Charlotte, United States
Server: gw2.clt1.slickvpn.com
5 nodes
Brisbane, Australia
Server: gw1.bne1.slickvpn.com
1 nodes
Tallinn, Estonia
Server: gw1.tll1.slickvpn.com
2 nodes
London, United Kingdom
Server: gw3.lhr2.slickvpn.com
1 nodes
Copenhagen, Denmark
Server: gw1.cph1.slickvpn.com
1 nodes
San Diego, United States
Server: gw1.san1.slickvpn.com
1 nodes
Phoenix, United States
Server: gw1.phx2.slickvpn.com
2 nodes
San Jose, United States
Server: gw1.sjc2.slickvpn.com
1 nodes
Boston, United States
Server: gw1.bos1.slickvpn.com
1 nodes
Vancouver, Canada
Server: gw1.yvr1.slickvpn.com
1 nodes
Chicago, United States
Server: gw2.ord1.slickvpn.com
1 nodes
Halmstad, Sweden
Server: gw1.had2.slickvpn.com
2 nodes
London, United Kingdom
Server: gw4.lhr1.slickvpn.com
1 nodes
Frankfurt, Germany
Server: gw1.fra1.slickvpn.com
1 nodes
Tel Aviv Yafo, Israel
Server: gw1.tlv2.slickvpn.com
1 nodes
Singapore, Singapore
Server: gw2.sin2.slickvpn.com
1 nodes
Dallas, United States
Server: gw1.dfw2.slickvpn.com
1 nodes
Stockholm, Sweden
Server: gw1.arn1.slickvpn.com
1 nodes
Eastleigh near Southampton, United Kingdom
Server: gw3.sou1.slickvpn.com
1 nodes
Seattle, WA, United States
Server: gw1.sea2.slickvpn.com
2 nodes
Belgrade, Serbia
Server: gw1.beg1.slickvpn.com
1 nodes
Prague, Czech Republic
Server: gw1.prg1.slickvpn.com
1 nodes
Milan, Italy
Server: gw1.mxp1.slickvpn.com
1 nodes
Newark, United States
Server: gw1.ewr1.slickvpn.com
1 nodes
Columbus, United States
Server: gw1.cmh1.slickvpn.com
2 nodes
Paris, France
Server: gw1.cdg1.slickvpn.com
1 nodes
Miami, United States
Server: gw2.mia4.slickvpn.com
1 nodes
Buffalo, NY, United States
Server: gw1.buf1.slickvpn.com
4 nodes
Prague, Czech Republic
Server: gw1.prg2.slickvpn.com
1 nodes
Reykjavik, Iceland
Server: gw1.rkv1.slickvpn.com
2 nodes
Prague, Czech Republic
Server: gw2.prg1.slickvpn.com
1 nodes
Denver, United States
Server: gw1.den1.slickvpn.com
5 nodes
Sydney, Australia
Server: gw1.syd2.slickvpn.com
1 nodes
London, United Kingdom
Server: gw2.lhr2.slickvpn.com
1 nodes
Chicago, United States
Server: gw1.ord4.slickvpn.com
4 nodes
Perth, Australia
Server: gw3.per1.slickvpn.com
1 nodes
Kiev, Ukraine
Server: gw1.iev1.slickvpn.com
1 nodes
Dubuque, United States
Server: gw1.dbq1.slickvpn.com
6 nodes
Montreal, Canada
Server: gw1.yul1.slickvpn.com
1 nodes
Frankfurt, Germany
Server: gw2.fra1.slickvpn.com
1 nodes
Washington, DC, United States
Server: gw2.iad1.slickvpn.com
5 nodes
Mumbai, India
Server: gw1.bom1.slickvpn.com
1 nodes
Washington, DC, United States
Server: gw3.iad1.slickvpn.com
5 nodes
Milan, Italy
Server: gw1.mxp2.slickvpn.com
1 nodes
Toronto, Canada
Server: gw1.yyz1.slickvpn.com
1 nodes
Dublin, Ireland
Server: gw1.dub1.slickvpn.com
1 nodes
Tokyo, Japan
Server: gw1.nrt1.slickvpn.com
1 nodes
Amsterdam, Netherlands
Server: gw1.ams3.slickvpn.com
1 nodes
Los Angeles, United States
Server: gw1.lax2.slickvpn.com
5 nodes
St Louis, United States
Server: gw1.stl1.slickvpn.com
1 nodes
London, United Kingdom
Server: gw1.lhr1.slickvpn.com
1 nodes
Isle Of Man, United Kingdom
Server: gw1.iom1.slickvpn.com
1 nodes
Palo Alto, United States
Server: gw3.pao1.slickvpn.com
1 nodes
Moscow, Russian Federation
Server: gw1.svo2.slickvpn.com
1 nodes
\ No newline at end of file
diff --git a/internal/provider/slickvpn/updater/updater.go b/internal/provider/slickvpn/updater/updater.go
new file mode 100644
index 00000000..f15a2f3a
--- /dev/null
+++ b/internal/provider/slickvpn/updater/updater.go
@@ -0,0 +1,22 @@
+package updater
+
+import (
+ "net/http"
+
+ "github.com/qdm12/gluetun/internal/provider/common"
+)
+
+type Updater struct {
+ client *http.Client
+ parallelResolver common.ParallelResolver
+ warner common.Warner
+}
+
+func New(client *http.Client, warner common.Warner,
+ parallelResolver common.ParallelResolver) *Updater {
+ return &Updater{
+ client: client,
+ parallelResolver: parallelResolver,
+ warner: warner,
+ }
+}
diff --git a/internal/provider/slickvpn/updater/website.go b/internal/provider/slickvpn/updater/website.go
new file mode 100644
index 00000000..15237f3a
--- /dev/null
+++ b/internal/provider/slickvpn/updater/website.go
@@ -0,0 +1,181 @@
+package updater
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+
+ htmlutils "github.com/qdm12/gluetun/internal/updater/html"
+ "golang.org/x/net/html"
+)
+
+func fetchServers(ctx context.Context, client *http.Client) (
+ hostToData map[string]serverData, err error) {
+ rootNode, err := fetchHTML(ctx, client)
+ if err != nil {
+ return nil, fmt.Errorf("fetching HTML code: %w", err)
+ }
+
+ hostToData, err = parseHTML(rootNode)
+ if err != nil {
+ return nil, fmt.Errorf("parsing HTML: %w", err)
+ }
+
+ return hostToData, nil
+}
+
+var ErrHTTPStatusCode = errors.New("HTTP status code is not OK")
+
+func fetchHTML(ctx context.Context, client *http.Client) (rootNode *html.Node, err error) {
+ const url = "https://www.slickvpn.com/locations/"
+ request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ response, err := client.Do(request)
+ if err != nil {
+ return nil, err
+ }
+
+ if response.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("%w: %d %s",
+ ErrHTTPStatusCode, response.StatusCode, response.Status)
+ }
+
+ rootNode, err = html.Parse(response.Body)
+ if err != nil {
+ _ = response.Body.Close()
+ return nil, fmt.Errorf("parsing HTML code: %w", err)
+ }
+
+ err = response.Body.Close()
+ if err != nil {
+ return nil, fmt.Errorf("closing response body: %w", err)
+ }
+
+ return rootNode, nil
+}
+
+type serverData struct {
+ ovpnURL string
+ country string
+ region string
+ city string
+}
+
+var (
+ ErrLocationTableNotFound = errors.New("HTML location table node not found")
+ ErrTbodyNotFound = errors.New("HTML tbody node not found")
+ ErrExtractOpenVPNURL = errors.New("failed extracting OpenVPN URL")
+)
+
+func parseHTML(rootNode *html.Node) (hostToData map[string]serverData, err error) {
+ locationTableNode := htmlutils.BFS(rootNode, matchLocationTable)
+ if locationTableNode == nil {
+ return nil, htmlutils.WrapError(ErrLocationTableNotFound, rootNode)
+ }
+
+ tBodyNode := htmlutils.BFS(locationTableNode, matchTbody)
+ if tBodyNode == nil {
+ return nil, htmlutils.WrapError(ErrTbodyNotFound, rootNode)
+ }
+
+ rowNodes := htmlutils.DirectChildren(tBodyNode, matchTr)
+ hostToData = make(map[string]serverData, len(rowNodes))
+
+ for _, rowNode := range rowNodes {
+ hostname, data, err := parseRowNode(rowNode)
+ if err != nil {
+ return nil, fmt.Errorf("parsing row node: %w", err)
+ }
+ hostToData[hostname] = data
+ }
+
+ return hostToData, nil
+}
+
+func parseRowNode(rowNode *html.Node) (hostname string, data serverData, err error) {
+ columnIndex := 0
+ const (
+ columnIndexContinent = 0
+ columnIndexCountry = 1
+ columnIndexCity = 2
+ columnIndexConfig = 3
+ )
+ for cellNode := rowNode.FirstChild; cellNode != nil; cellNode = cellNode.NextSibling {
+ if cellNode.FirstChild == nil {
+ continue
+ }
+
+ switch columnIndex {
+ case columnIndexContinent:
+ data.region = cellNode.FirstChild.Data
+ case columnIndexCountry:
+ data.country = cellNode.FirstChild.Data
+ case columnIndexCity:
+ data.city = cellNode.FirstChild.Data
+ case columnIndexConfig:
+ linkNodes := htmlutils.DirectChildren(cellNode, matchA)
+ for _, linkNode := range linkNodes {
+ if linkNode.FirstChild.Data != "OpenVPN" {
+ continue
+ }
+
+ data.ovpnURL = htmlutils.Attribute(linkNode, "href")
+ if data.ovpnURL == "" {
+ return "", data, htmlutils.WrapError(ErrExtractOpenVPNURL, linkNode)
+ }
+
+ hostname, err = extractHostnameFromURL(data.ovpnURL)
+ if err != nil {
+ return "", data, fmt.Errorf("extracting hostname from url: %w", err)
+ }
+
+ break
+ }
+ }
+
+ columnIndex++
+ if columnIndex == columnIndexConfig+1 {
+ break
+ }
+ }
+
+ return hostname, data, nil
+}
+
+func matchLocationTable(rootNode *html.Node) (match bool) {
+ return htmlutils.MatchID("location-table")(rootNode)
+}
+
+func matchTbody(locationTableNode *html.Node) (match bool) {
+ return htmlutils.MatchData("tbody")(locationTableNode)
+}
+
+func matchTr(tbodyNode *html.Node) (match bool) {
+ return htmlutils.MatchData("tr")(tbodyNode)
+}
+
+func matchA(cellNode *html.Node) (match bool) {
+ return htmlutils.MatchData("a")(cellNode)
+}
+
+var serverNameRegex = regexp.MustCompile(`^.+\/(?P.+)\.ovpn$`)
+
+var (
+ ErrExtractHostnameFromURL = errors.New("cannot extract hostname from url")
+)
+
+func extractHostnameFromURL(url string) (hostname string, err error) {
+ matches := serverNameRegex.FindStringSubmatch(url)
+ const minMatches = 2
+ if len(matches) < minMatches {
+ return "", fmt.Errorf("%w: %s has less than 2 matches for %s",
+ ErrExtractHostnameFromURL, url, serverNameRegex)
+ }
+ hostname = matches[1]
+ return hostname, nil
+}
diff --git a/internal/provider/slickvpn/updater/website_test.go b/internal/provider/slickvpn/updater/website_test.go
new file mode 100644
index 00000000..1c251cbe
--- /dev/null
+++ b/internal/provider/slickvpn/updater/website_test.go
@@ -0,0 +1,337 @@
+package updater
+
+import (
+ "context"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/net/html"
+)
+
+type roundTripFunc func(r *http.Request) (*http.Response, error)
+
+func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
+ return f(r)
+}
+
+func Test_fetchServers(t *testing.T) {
+ t.Parallel()
+
+ canceledCtx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ testCases := map[string]struct {
+ ctx context.Context
+ responseStatus int
+ responseBody io.ReadCloser
+ hostToData map[string]serverData
+ errWrapped error
+ errMessage string
+ }{
+ "context canceled": {
+ ctx: canceledCtx,
+ errWrapped: context.Canceled,
+ errMessage: `fetching HTML code: Get "https://www.slickvpn.com/locations/": context canceled`,
+ },
+ "success": {
+ ctx: context.Background(),
+ responseStatus: http.StatusOK,
+ //nolint:lll
+ responseBody: ioutil.NopCloser(strings.NewReader(`
+
+ `)),
+ hostToData: map[string]serverData{
+ "gw1.kna1.slickvpn.com": {
+ ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kna1.slickvpn.com.ovpn", //nolint:lll
+ country: "Chile",
+ region: "South America",
+ city: "Vina del Mar",
+ },
+ },
+ },
+ }
+
+ for name, testCase := range testCases {
+ testCase := testCase
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ client := &http.Client{
+ Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
+ assert.Equal(t, http.MethodGet, r.Method)
+ assert.Equal(t, r.URL.String(), "https://www.slickvpn.com/locations/")
+
+ ctxErr := r.Context().Err()
+ if ctxErr != nil {
+ return nil, ctxErr
+ }
+
+ return &http.Response{
+ StatusCode: http.StatusOK,
+ Status: http.StatusText(testCase.responseStatus),
+ Body: testCase.responseBody,
+ }, nil
+ }),
+ }
+
+ hostToData, err := fetchServers(testCase.ctx, client)
+
+ assert.ErrorIs(t, err, testCase.errWrapped)
+ if testCase.errWrapped != nil {
+ assert.EqualError(t, err, testCase.errMessage)
+ }
+ assert.Equal(t, testCase.hostToData, hostToData)
+ })
+ }
+}
+
+func Test_fetchHTML(t *testing.T) {
+ t.Parallel()
+
+ canceledCtx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ testCases := map[string]struct {
+ ctx context.Context
+ responseStatus int
+ responseBody io.ReadCloser
+ rootNode *html.Node
+ errWrapped error
+ errMessage string
+ }{
+ "context canceled": {
+ ctx: canceledCtx,
+ errWrapped: context.Canceled,
+ errMessage: `Get "https://www.slickvpn.com/locations/": context canceled`,
+ },
+ "response status not ok": {
+ ctx: context.Background(),
+ responseStatus: http.StatusNotFound,
+ errWrapped: ErrHTTPStatusCode,
+ errMessage: `HTTP status code is not OK: 404 Not Found`,
+ },
+ "success": {
+ ctx: context.Background(),
+ responseStatus: http.StatusOK,
+ rootNode: parseTestHTML(t, "some body"),
+ responseBody: ioutil.NopCloser(strings.NewReader("some body")),
+ },
+ }
+
+ for name, testCase := range testCases {
+ testCase := testCase
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ client := &http.Client{
+ Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
+ assert.Equal(t, http.MethodGet, r.Method)
+ assert.Equal(t, r.URL.String(), "https://www.slickvpn.com/locations/")
+
+ ctxErr := r.Context().Err()
+ if ctxErr != nil {
+ return nil, ctxErr
+ }
+
+ return &http.Response{
+ StatusCode: testCase.responseStatus,
+ Status: http.StatusText(testCase.responseStatus),
+ Body: testCase.responseBody,
+ }, nil
+ }),
+ }
+
+ rootNode, err := fetchHTML(testCase.ctx, client)
+
+ assert.ErrorIs(t, err, testCase.errWrapped)
+ if testCase.errWrapped != nil {
+ assert.EqualError(t, err, testCase.errMessage)
+ }
+ assert.Equal(t, testCase.rootNode, rootNode)
+ })
+ }
+}
+
+func Test_parseHTML(t *testing.T) {
+ t.Parallel()
+
+ testCases := map[string]struct {
+ rootNode *html.Node
+ hostToData map[string]serverData
+ errWrapped error
+ errMessage string
+ }{
+ "empty html": {
+ rootNode: parseTestHTML(t, ""),
+ errWrapped: ErrLocationTableNotFound,
+ errMessage: `HTML location table node not found: in HTML code: `,
+ },
+ "test data": {
+ rootNode: parseTestDataIndexHTML(t),
+ //nolint:lll
+ hostToData: map[string]serverData{
+ "gw1.ams1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams1.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
+ "gw1.ams2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams2.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
+ "gw1.ams3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams3.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
+ "gw1.ams4.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams4.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
+ "gw1.arn1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.arn1.slickvpn.com.ovpn", country: "Sweden", region: "Europe", city: "Stockholm"},
+ "gw1.arn3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.arn3.slickvpn.com.ovpn", country: "Sweden", region: "Europe", city: "Stockholm"},
+ "gw1.ath1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ath1.slickvpn.com.ovpn", country: "Greece", region: "Europe", city: "Athens"},
+ "gw1.atl1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.atl1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Atlanta"},
+ "gw1.atl3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.atl3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Atlanta"},
+ "gw1.beg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.beg1.slickvpn.com.ovpn", country: "Serbia", region: "Europe", city: "Belgrade"},
+ "gw1.bkk1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bkk1.slickvpn.com.ovpn", country: "Thailand", region: "Asia", city: "Bangkok"},
+ "gw1.blr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.blr1.slickvpn.com.ovpn", country: "India", region: "Asia", city: "Bangalore"},
+ "gw1.bne1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bne1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Brisbane"},
+ "gw1.bom1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bom1.slickvpn.com.ovpn", country: "India", region: "Asia", city: "Mumbai"},
+ "gw1.bos1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bos1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Boston"},
+ "gw1.bud1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bud1.slickvpn.com.ovpn", country: "Hungary", region: "Europe", city: "Budapest"},
+ "gw1.buf1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.buf1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Buffalo"},
+ "gw1.buh2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.buh2.slickvpn.com.ovpn", country: "Romania", region: "Europe", city: "Bucharest"},
+ "gw1.cdg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cdg1.slickvpn.com.ovpn", country: "France", region: "Europe", city: "Paris"},
+ "gw1.cgk1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cgk1.slickvpn.com.ovpn", country: "Indonesia", region: "Asia", city: "Jakarta"},
+ "gw1.cmh1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cmh1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Columbus"},
+ "gw1.cph1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cph1.slickvpn.com.ovpn", country: "Denmark", region: "Europe", city: "Copenhagen"},
+ "gw1.cvt1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cvt1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Coventry"},
+ "gw1.dbq1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dbq1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dubuque"},
+ "gw1.den1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.den1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Denver"},
+ "gw1.dfw2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dfw2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
+ "gw1.dfw3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dfw3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
+ "gw1.dub1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dub1.slickvpn.com.ovpn", country: "Ireland", region: "Europe", city: "Dublin"},
+ "gw1.ewr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ewr1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Newark"},
+ "gw1.fra1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.fra1.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Frankfurt"},
+ "gw1.fra2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.fra2.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Frankfurt"},
+ "gw1.gru2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.gru2.slickvpn.com.ovpn", country: "Brazil", region: "South America", city: "Sao Paulo"},
+ "gw1.grz1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.grz1.slickvpn.com.ovpn", country: "Austria", region: "Europe", city: "Graz"},
+ "gw1.had2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.had2.slickvpn.com.ovpn", country: "Sweden", region: "Europe", city: "Halmstad"},
+ "gw1.hkg2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.hkg2.slickvpn.com.ovpn", country: "Hong Kong", region: "Asia", city: "Hong Kong"},
+ "gw1.iad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.iad1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Washington"},
+ "gw1.iev1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.iev1.slickvpn.com.ovpn", country: "Ukraine", region: "Europe", city: "Kiev"},
+ "gw1.iom1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.iom1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Isle Of Man"},
+ "gw1.kiv1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kiv1.slickvpn.com.ovpn", country: "Moldova", region: "Europe", city: "Chisinau"},
+ "gw1.kna1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kna1.slickvpn.com.ovpn", country: "Chile", region: "South America", city: "Vina del Mar"},
+ "gw1.kul1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kul1.slickvpn.com.ovpn", country: "Malaysia", region: "Asia", city: "Kuala Lumpur"},
+ "gw1.las1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.las1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Las Vegas"},
+ "gw1.lax1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lax1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Los Angeles"},
+ "gw1.lax2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lax2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Los Angeles"},
+ "gw1.led1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.led1.slickvpn.com.ovpn", country: "Russian Federation", region: "Europe", city: "St Petersburg"},
+ "gw1.lga1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lga1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "New York"},
+ "gw1.lga2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lga2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "New York"},
+ "gw1.lhr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lhr1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
+ "gw1.lhr2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lhr2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
+ "gw1.lil1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lil1.slickvpn.com.ovpn", country: "France", region: "Europe", city: "Lille"},
+ "gw1.lju1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lju1.slickvpn.com.ovpn", country: "Slovenia", region: "Europe", city: "Ljubljana"},
+ "gw1.mad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mad1.slickvpn.com.ovpn", country: "Spain", region: "Europe", city: "Madrid"},
+ "gw1.man2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.man2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Manchester"},
+ "gw1.mci2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mci2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Kansas City"},
+ "gw1.mrn1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mrn1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Morganton"},
+ "gw1.mxp1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mxp1.slickvpn.com.ovpn", country: "Italy", region: "Europe", city: "Milan"},
+ "gw1.mxp2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mxp2.slickvpn.com.ovpn", country: "Italy", region: "Europe", city: "Milan"},
+ "gw1.nrt1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.nrt1.slickvpn.com.ovpn", country: "Japan", region: "Asia", city: "Tokyo"},
+ "gw1.nue1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.nue1.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Nürnberg"},
+ "gw1.ord3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ord3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Chicago"},
+ "gw1.ord4.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ord4.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Chicago"},
+ "gw1.ost2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ost2.slickvpn.com.ovpn", country: "Belgium", region: "Europe", city: "Ostend"},
+ "gw1.pao1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.pao1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Palo Alto"},
+ "gw1.phx2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.phx2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Phoenix"},
+ "gw1.prg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.prg1.slickvpn.com.ovpn", country: "Czech Republic", region: "Europe", city: "Prague"},
+ "gw1.prg2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.prg2.slickvpn.com.ovpn", country: "Czech Republic", region: "Europe", city: "Prague"},
+ "gw1.rcs1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.rcs1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Rochester"},
+ "gw1.rkv1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.rkv1.slickvpn.com.ovpn", country: "Iceland", region: "Europe", city: "Reykjavik"},
+ "gw1.san1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.san1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "San Diego"},
+ "gw1.sea1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sea1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Seattle"},
+ "gw1.sea2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sea2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Seattle"},
+ "gw1.sin1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sin1.slickvpn.com.ovpn", country: "Singapore", region: "Asia", city: "Singapore"},
+ "gw1.sin2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sin2.slickvpn.com.ovpn", country: "Singapore", region: "Asia", city: "Singapore"},
+ "gw1.sjc2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sjc2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "San Jose"},
+ "gw1.skg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.skg1.slickvpn.com.ovpn", country: "Greece", region: "Europe", city: "Thessaloniki"},
+ "gw1.sou1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sou1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Eastleigh near Southampton"},
+ "gw1.stl1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.stl1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "St Louis"},
+ "gw1.svo1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.svo1.slickvpn.com.ovpn", country: "Russian Federation", region: "Europe", city: "Moscow"},
+ "gw1.svo2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.svo2.slickvpn.com.ovpn", country: "Russian Federation", region: "Europe", city: "Moscow"},
+ "gw1.syd1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.syd1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Sydney"},
+ "gw1.syd2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.syd2.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Sydney"},
+ "gw1.tll1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.tll1.slickvpn.com.ovpn", country: "Estonia", region: "Europe", city: "Tallinn"},
+ "gw1.tlv2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.tlv2.slickvpn.com.ovpn", country: "Israel", region: "Asia", city: "Tel Aviv Yafo"},
+ "gw1.tpa1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.tpa1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Tampa"},
+ "gw1.trf1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.trf1.slickvpn.com.ovpn", country: "Norway", region: "Europe", city: "Torp"},
+ "gw1.waw1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.waw1.slickvpn.com.ovpn", country: "Poland", region: "Europe", city: "Warsaw"},
+ "gw1.yei1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yei1.slickvpn.com.ovpn", country: "Turkey", region: "Asia", city: "Bursa"},
+ "gw1.yul1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yul1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Montreal"},
+ "gw1.yul2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yul2.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Montreal"},
+ "gw1.yvr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yvr1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Vancouver"},
+ "gw1.yyz1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yyz1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Toronto"},
+ "gw1.zrh1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.zrh1.slickvpn.com.ovpn", country: "Switzerland", region: "Europe", city: "Zurich"},
+ "gw2.ams3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ams3.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
+ "gw2.atl3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.atl3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Atlanta"},
+ "gw2.bcn2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.bcn2.slickvpn.com.ovpn", country: "Spain", region: "Europe", city: "Barcelona"},
+ "gw2.clt1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.clt1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Charlotte"},
+ "gw2.dfw3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.dfw3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
+ "gw2.ewr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ewr1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Newark"},
+ "gw2.fra1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.fra1.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Frankfurt"},
+ "gw2.hou1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.hou1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Houston"},
+ "gw2.iad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.iad1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Washington"},
+ "gw2.lhr2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.lhr2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
+ "gw2.mel1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mel1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Melbourne"},
+ "gw2.mia3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mia3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Miami"},
+ "gw2.mia4.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mia4.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Miami"},
+ "gw2.mxp2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mxp2.slickvpn.com.ovpn", country: "Italy", region: "Europe", city: "Milan"},
+ "gw2.ord1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ord1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Chicago"},
+ "gw2.ost2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ost2.slickvpn.com.ovpn", country: "Belgium", region: "Europe", city: "Ostend"},
+ "gw2.pao1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.pao1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Palo Alto"},
+ "gw2.prg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.prg1.slickvpn.com.ovpn", country: "Czech Republic", region: "Europe", city: "Prague"},
+ "gw2.pty1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.pty1.slickvpn.com.ovpn", country: "Panama", region: "North America", city: "Panama City"},
+ "gw2.sin2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.sin2.slickvpn.com.ovpn", country: "Singapore", region: "Asia", city: "Singapore"},
+ "gw2.slc1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.slc1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Salt Lake City"},
+ "gw2.syd2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.syd2.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Sydney"},
+ "gw2.tpe1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.tpe1.slickvpn.com.ovpn", country: "Taiwan", region: "Asia", city: "Taipei"},
+ "gw2.yul2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.yul2.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Montreal"},
+ "gw2.yyz1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.yyz1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Toronto"},
+ "gw3.dfw3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.dfw3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
+ "gw3.ewr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.ewr1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Newark"},
+ "gw3.iad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.iad1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Washington"},
+ "gw3.lax3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.lax3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Los Angeles"},
+ "gw3.lhr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.lhr1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
+ "gw3.lhr2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.lhr2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
+ "gw3.pao1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.pao1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Palo Alto"},
+ "gw3.per1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.per1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Perth"},
+ "gw3.sou1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.sou1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Eastleigh near Southampton"},
+ "gw4.lhr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw4.lhr1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
+ },
+ },
+ }
+
+ for name, testCase := range testCases {
+ testCase := testCase
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ hostToData, err := parseHTML(testCase.rootNode)
+
+ assert.Equal(t, testCase.hostToData, hostToData)
+ assert.ErrorIs(t, err, testCase.errWrapped)
+ if testCase.errWrapped != nil {
+ assert.EqualError(t, err, testCase.errMessage)
+ }
+ })
+ }
+}
diff --git a/internal/storage/servers.json b/internal/storage/servers.json
index 385d134b..f32c7729 100644
--- a/internal/storage/servers.json
+++ b/internal/storage/servers.json
@@ -113317,6 +113317,1409 @@
}
]
},
+ "slickvpn": {
+ "version": 1,
+ "timestamp": 1660572114,
+ "servers": [
+ {
+ "vpn": "openvpn",
+ "country": "Australia",
+ "region": "Oceania",
+ "city": "Brisbane",
+ "hostname": "gw1.bne1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "43.245.160.142"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Australia",
+ "region": "Oceania",
+ "city": "Melbourne",
+ "hostname": "gw2.mel1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "113.52.144.50"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Australia",
+ "region": "Oceania",
+ "city": "Perth",
+ "hostname": "gw3.per1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "103.231.89.206"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Australia",
+ "region": "Oceania",
+ "city": "Sydney",
+ "hostname": "gw1.syd1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "163.47.126.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Australia",
+ "region": "Oceania",
+ "city": "Sydney",
+ "hostname": "gw1.syd2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.105.173.66"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Australia",
+ "region": "Oceania",
+ "city": "Sydney",
+ "hostname": "gw2.syd2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.105.168.12"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Austria",
+ "region": "Europe",
+ "city": "Graz",
+ "hostname": "gw1.grz1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "158.255.212.60"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "region": "Europe",
+ "city": "Ostend",
+ "hostname": "gw1.ost2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.71.249.22"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Belgium",
+ "region": "Europe",
+ "city": "Ostend",
+ "hostname": "gw2.ost2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.71.249.81"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Brazil",
+ "region": "South America",
+ "city": "Sao Paulo",
+ "hostname": "gw1.gru2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "177.54.158.153"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "region": "North America",
+ "city": "Montreal",
+ "hostname": "gw1.yul1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "205.204.85.246"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "region": "North America",
+ "city": "Montreal",
+ "hostname": "gw1.yul2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "173.209.62.178"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "region": "North America",
+ "city": "Montreal",
+ "hostname": "gw2.yul2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "67.215.7.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "region": "North America",
+ "city": "Toronto",
+ "hostname": "gw1.yyz1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.105.14.142"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "region": "North America",
+ "city": "Toronto",
+ "hostname": "gw2.yyz1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.105.9.120"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Canada",
+ "region": "North America",
+ "city": "Vancouver",
+ "hostname": "gw1.yvr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.111.132.46"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Chile",
+ "region": "South America",
+ "city": "Vina del Mar",
+ "hostname": "gw1.kna1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "37.235.52.26"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "region": "Europe",
+ "city": "Prague",
+ "hostname": "gw1.prg1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "46.36.36.51"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "region": "Europe",
+ "city": "Prague",
+ "hostname": "gw1.prg2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "193.235.207.24"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Czech Republic",
+ "region": "Europe",
+ "city": "Prague",
+ "hostname": "gw2.prg1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "46.36.36.98"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Denmark",
+ "region": "Europe",
+ "city": "Copenhagen",
+ "hostname": "gw1.cph1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "93.90.116.25"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Estonia",
+ "region": "Europe",
+ "city": "Tallinn",
+ "hostname": "gw1.tll1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.155.96.107"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "region": "Europe",
+ "city": "Lille",
+ "hostname": "gw1.lil1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "5.135.81.27"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "France",
+ "region": "Europe",
+ "city": "Paris",
+ "hostname": "gw1.cdg1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "176.67.168.241"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "region": "Europe",
+ "city": "Frankfurt",
+ "hostname": "gw1.fra1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "139.162.159.184"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "region": "Europe",
+ "city": "Frankfurt",
+ "hostname": "gw1.fra2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "109.123.80.62"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "region": "Europe",
+ "city": "Frankfurt",
+ "hostname": "gw2.fra1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "139.162.175.126"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Germany",
+ "region": "Europe",
+ "city": "Nürnberg",
+ "hostname": "gw1.nue1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "31.7.187.233"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "region": "Europe",
+ "city": "Athens",
+ "hostname": "gw1.ath1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.226.65.178"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Greece",
+ "region": "Europe",
+ "city": "Thessaloniki",
+ "hostname": "gw1.skg1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.71.166.19"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hong Kong",
+ "region": "Asia",
+ "city": "Hong Kong",
+ "hostname": "gw1.hkg2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "158.255.208.240"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Hungary",
+ "region": "Europe",
+ "city": "Budapest",
+ "hostname": "gw1.bud1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "91.219.239.203"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Iceland",
+ "region": "Europe",
+ "city": "Reykjavik",
+ "hostname": "gw1.rkv1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "37.235.49.172"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "region": "Asia",
+ "city": "Bangalore",
+ "hostname": "gw1.blr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "139.59.42.121"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "India",
+ "region": "Asia",
+ "city": "Mumbai",
+ "hostname": "gw1.bom1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.105.63.74"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Indonesia",
+ "region": "Asia",
+ "city": "Jakarta",
+ "hostname": "gw1.cgk1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "119.235.252.142"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ireland",
+ "region": "Europe",
+ "city": "Dublin",
+ "hostname": "gw1.dub1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "92.51.242.25"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Israel",
+ "region": "Asia",
+ "city": "Tel Aviv Yafo",
+ "hostname": "gw1.tlv2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "193.182.144.37"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "region": "Europe",
+ "city": "Milan",
+ "hostname": "gw1.mxp1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "37.247.49.148"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "region": "Europe",
+ "city": "Milan",
+ "hostname": "gw1.mxp2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "149.154.157.221"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Italy",
+ "region": "Europe",
+ "city": "Milan",
+ "hostname": "gw2.mxp2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "149.154.157.112"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Japan",
+ "region": "Asia",
+ "city": "Tokyo",
+ "hostname": "gw1.nrt1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.105.235.210"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Malaysia",
+ "region": "Asia",
+ "city": "Kuala Lumpur",
+ "hostname": "gw1.kul1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "117.53.155.94"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Moldova",
+ "region": "Europe",
+ "city": "Chisinau",
+ "hostname": "gw1.kiv1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.121.87.27"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "region": "Europe",
+ "city": "Amsterdam",
+ "hostname": "gw1.ams1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "81.4.102.9"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "region": "Europe",
+ "city": "Amsterdam",
+ "hostname": "gw1.ams2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.80.222.63"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "region": "Europe",
+ "city": "Amsterdam",
+ "hostname": "gw1.ams3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "151.236.14.29"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "region": "Europe",
+ "city": "Amsterdam",
+ "hostname": "gw1.ams4.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.34.136.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Netherlands",
+ "region": "Europe",
+ "city": "Amsterdam",
+ "hostname": "gw2.ams3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "151.236.14.57"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Norway",
+ "region": "Europe",
+ "city": "Torp",
+ "hostname": "gw1.trf1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.125.169.11"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Panama",
+ "region": "North America",
+ "city": "Panama City",
+ "hostname": "gw2.pty1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "181.174.164.64"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Poland",
+ "region": "Europe",
+ "city": "Warsaw",
+ "hostname": "gw1.waw1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "212.7.220.28"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Romania",
+ "region": "Europe",
+ "city": "Bucharest",
+ "hostname": "gw1.buh2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "89.46.100.116"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "region": "Europe",
+ "city": "Moscow",
+ "hostname": "gw1.svo1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "46.38.62.29"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "region": "Europe",
+ "city": "Moscow",
+ "hostname": "gw1.svo2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "213.183.56.34"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Russian Federation",
+ "region": "Europe",
+ "city": "St Petersburg",
+ "hostname": "gw1.led1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "213.183.54.28"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Serbia",
+ "region": "Europe",
+ "city": "Belgrade",
+ "hostname": "gw1.beg1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "194.71.126.18"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "region": "Asia",
+ "city": "Singapore",
+ "hostname": "gw1.sin1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "199.195.193.181"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "region": "Asia",
+ "city": "Singapore",
+ "hostname": "gw1.sin2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "139.162.23.150"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Singapore",
+ "region": "Asia",
+ "city": "Singapore",
+ "hostname": "gw2.sin2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "139.162.55.80"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Slovenia",
+ "region": "Europe",
+ "city": "Ljubljana",
+ "hostname": "gw1.lju1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.71.244.13"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Spain",
+ "region": "Europe",
+ "city": "Barcelona",
+ "hostname": "gw2.bcn2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "5.134.119.168"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Spain",
+ "region": "Europe",
+ "city": "Madrid",
+ "hostname": "gw1.mad1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "37.235.53.46"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Sweden",
+ "region": "Europe",
+ "city": "Halmstad",
+ "hostname": "gw1.had2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "109.74.11.69"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Sweden",
+ "region": "Europe",
+ "city": "Stockholm",
+ "hostname": "gw1.arn1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "178.73.210.224"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Sweden",
+ "region": "Europe",
+ "city": "Stockholm",
+ "hostname": "gw1.arn3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "159.253.26.130"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Switzerland",
+ "region": "Europe",
+ "city": "Zurich",
+ "hostname": "gw1.zrh1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "178.209.51.203"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Taiwan",
+ "region": "Asia",
+ "city": "Taipei",
+ "hostname": "gw2.tpe1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "103.98.73.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Thailand",
+ "region": "Asia",
+ "city": "Bangkok",
+ "hostname": "gw1.bkk1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "124.109.1.191"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Turkey",
+ "region": "Asia",
+ "city": "Bursa",
+ "hostname": "gw1.yei1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "45.123.119.43"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "Ukraine",
+ "region": "Europe",
+ "city": "Kiev",
+ "hostname": "gw1.iev1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "176.107.177.226"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "Coventry",
+ "hostname": "gw1.cvt1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "77.74.195.22"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "Eastleigh near Southampton",
+ "hostname": "gw1.sou1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "78.143.255.174"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "Eastleigh near Southampton",
+ "hostname": "gw3.sou1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "78.143.255.175"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "Isle Of Man",
+ "hostname": "gw1.iom1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "37.235.55.43"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "London",
+ "hostname": "gw1.lhr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "109.123.122.214"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "London",
+ "hostname": "gw1.lhr2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "176.58.120.179"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "London",
+ "hostname": "gw2.lhr2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "178.79.164.214"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "London",
+ "hostname": "gw3.lhr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "37.123.117.149"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "London",
+ "hostname": "gw3.lhr2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "139.162.220.234"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "London",
+ "hostname": "gw4.lhr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "146.185.17.165"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "Manchester",
+ "hostname": "gw1.man2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "91.109.246.6"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United Kingdom",
+ "region": "Europe",
+ "city": "Rochester",
+ "hostname": "gw1.rcs1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "91.227.223.185"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Atlanta",
+ "hostname": "gw1.atl1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "216.119.148.130"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Atlanta",
+ "hostname": "gw1.atl3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "23.239.17.108"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Atlanta",
+ "hostname": "gw2.atl3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "45.79.221.197"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Boston",
+ "hostname": "gw1.bos1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.34.83.144"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Buffalo",
+ "hostname": "gw1.buf1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "23.94.191.122"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Charlotte",
+ "hostname": "gw2.clt1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.154.255.210"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Chicago",
+ "hostname": "gw1.ord3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "64.187.232.74"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Chicago",
+ "hostname": "gw1.ord4.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "206.217.136.90"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Chicago",
+ "hostname": "gw2.ord1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "174.127.124.132"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Columbus",
+ "hostname": "gw1.cmh1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "207.182.134.3"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Dallas",
+ "hostname": "gw1.dfw2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "174.127.75.120"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Dallas",
+ "hostname": "gw1.dfw3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "96.126.113.159"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Dallas",
+ "hostname": "gw2.dfw3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "198.58.117.6"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Dallas",
+ "hostname": "gw3.dfw3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "104.237.128.245"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Denver",
+ "hostname": "gw1.den1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "23.237.26.56"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Dubuque",
+ "hostname": "gw1.dbq1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "199.102.44.15"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Houston",
+ "hostname": "gw2.hou1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "162.218.229.130"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Kansas City",
+ "hostname": "gw1.mci2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.187.101.186"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Las Vegas",
+ "hostname": "gw1.las1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "104.143.10.8"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Los Angeles",
+ "hostname": "gw1.lax1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "172.245.220.35"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Los Angeles",
+ "hostname": "gw1.lax2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "104.247.220.10"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Los Angeles",
+ "hostname": "gw3.lax3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "107.160.101.199"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Miami",
+ "hostname": "gw2.mia3.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.171.19.130"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Miami",
+ "hostname": "gw2.mia4.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "199.127.62.98"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Morganton",
+ "hostname": "gw1.mrn1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "199.241.190.29"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "New York",
+ "hostname": "gw1.lga1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "206.217.128.67"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "New York",
+ "hostname": "gw1.lga2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "206.221.178.210"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Newark",
+ "hostname": "gw1.ewr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "45.33.81.117"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Newark",
+ "hostname": "gw2.ewr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "162.216.19.236"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Newark",
+ "hostname": "gw3.ewr1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "50.116.54.120"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Palo Alto",
+ "hostname": "gw1.pao1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "192.81.128.78"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Palo Alto",
+ "hostname": "gw2.pao1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "45.79.88.135"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Palo Alto",
+ "hostname": "gw3.pao1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "173.255.243.165"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Phoenix",
+ "hostname": "gw1.phx2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "198.15.66.67"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Salt Lake City",
+ "hostname": "gw2.slc1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "209.95.48.179"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "San Diego",
+ "hostname": "gw1.san1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "108.161.151.123"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "San Jose",
+ "hostname": "gw1.sjc2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "199.195.192.104"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Seattle",
+ "hostname": "gw1.sea1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "107.181.191.68"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Seattle",
+ "hostname": "gw1.sea2.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "167.88.112.78"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "St Louis",
+ "hostname": "gw1.stl1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "209.135.132.136"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Tampa",
+ "hostname": "gw1.tpa1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "104.156.54.182"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Washington",
+ "hostname": "gw1.iad1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.151.12.218"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Washington",
+ "hostname": "gw2.iad1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.151.12.226"
+ ]
+ },
+ {
+ "vpn": "openvpn",
+ "country": "United States",
+ "region": "North America",
+ "city": "Washington",
+ "hostname": "gw3.iad1.slickvpn.com",
+ "udp": true,
+ "ips": [
+ "185.151.12.242"
+ ]
+ }
+ ]
+ },
"surfshark": {
"version": 4,
"timestamp": 1654479618,
diff --git a/internal/updater/html/attribute.go b/internal/updater/html/attribute.go
new file mode 100644
index 00000000..fc833c96
--- /dev/null
+++ b/internal/updater/html/attribute.go
@@ -0,0 +1,12 @@
+package html
+
+import "golang.org/x/net/html"
+
+func Attribute(node *html.Node, key string) (value string) {
+ for _, attribute := range node.Attr {
+ if attribute.Key == key {
+ return attribute.Val
+ }
+ }
+ return ""
+}
diff --git a/internal/updater/html/bfs.go b/internal/updater/html/bfs.go
new file mode 100644
index 00000000..5d2374ae
--- /dev/null
+++ b/internal/updater/html/bfs.go
@@ -0,0 +1,43 @@
+package html
+
+import (
+ "container/list"
+ "fmt"
+
+ "golang.org/x/net/html"
+)
+
+// BFS returns the node matching the match function and nil
+// if no node is found.
+func BFS(rootNode *html.Node, match MatchFunc) (node *html.Node) {
+ visited := make(map[*html.Node]struct{})
+ queue := list.New()
+ _ = queue.PushBack(rootNode)
+
+ for queue.Len() > 0 {
+ listElement := queue.Front()
+ node, ok := queue.Remove(listElement).(*html.Node)
+ if !ok {
+ panic(fmt.Sprintf("linked list has bad type %T", listElement.Value))
+ }
+
+ if node == nil {
+ continue
+ }
+
+ if _, ok := visited[node]; ok {
+ continue
+ }
+ visited[node] = struct{}{}
+
+ if match(node) {
+ return node
+ }
+
+ for child := node.FirstChild; child != nil; child = child.NextSibling {
+ _ = queue.PushBack(child)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/updater/html/css.go b/internal/updater/html/css.go
new file mode 100644
index 00000000..10adfe0c
--- /dev/null
+++ b/internal/updater/html/css.go
@@ -0,0 +1,22 @@
+package html
+
+import (
+ "strings"
+
+ "golang.org/x/net/html"
+)
+
+func HasClassStrings(node *html.Node, classStrings ...string) (match bool) {
+ targetClasses := make(map[string]struct{}, len(classStrings))
+ for _, classString := range classStrings {
+ targetClasses[classString] = struct{}{}
+ }
+
+ classAttribute := Attribute(node, "class")
+ classes := strings.Fields(classAttribute)
+ for _, class := range classes {
+ delete(targetClasses, class)
+ }
+
+ return len(targetClasses) == 0
+}
diff --git a/internal/updater/html/errors.go b/internal/updater/html/errors.go
new file mode 100644
index 00000000..29c0979c
--- /dev/null
+++ b/internal/updater/html/errors.go
@@ -0,0 +1,27 @@
+package html
+
+import (
+ "bytes"
+ "fmt"
+
+ "golang.org/x/net/html"
+)
+
+func WrapError(sentinelError error, node *html.Node) error {
+ return fmt.Errorf("%w: in HTML code: %s",
+ sentinelError, mustRenderHTML(node))
+}
+
+func WrapWarning(warning string, node *html.Node) string {
+ return fmt.Sprintf("%s: in HTML code: %s",
+ warning, mustRenderHTML(node))
+}
+
+func mustRenderHTML(node *html.Node) (rendered string) {
+ stringBuffer := bytes.NewBufferString("")
+ err := html.Render(stringBuffer, node)
+ if err != nil {
+ panic(err)
+ }
+ return stringBuffer.String()
+}
diff --git a/internal/updater/html/match.go b/internal/updater/html/match.go
new file mode 100644
index 00000000..425da58e
--- /dev/null
+++ b/internal/updater/html/match.go
@@ -0,0 +1,43 @@
+package html
+
+import (
+ "golang.org/x/net/html"
+)
+
+type MatchFunc func(node *html.Node) (match bool)
+
+func MatchID(id string) MatchFunc {
+ return func(node *html.Node) (match bool) {
+ if node == nil {
+ return false
+ }
+
+ return Attribute(node, "id") == id
+ }
+}
+
+func MatchData(data string) MatchFunc {
+ return func(node *html.Node) (match bool) {
+ return node != nil && node.Type == html.ElementNode && node.Data == data
+ }
+}
+
+func DirectChild(parent *html.Node,
+ matchFunc MatchFunc) (child *html.Node) {
+ for child := parent.FirstChild; child != nil; child = child.NextSibling {
+ if matchFunc(child) {
+ return child
+ }
+ }
+ return nil
+}
+
+func DirectChildren(parent *html.Node,
+ matchFunc MatchFunc) (children []*html.Node) {
+ for child := parent.FirstChild; child != nil; child = child.NextSibling {
+ if matchFunc(child) {
+ children = append(children, child)
+ }
+ }
+ return children
+}
diff --git a/internal/updater/openvpn/extract.go b/internal/updater/openvpn/extract.go
index 53c7e00d..33339755 100644
--- a/internal/updater/openvpn/extract.go
+++ b/internal/updater/openvpn/extract.go
@@ -76,6 +76,7 @@ func ExtractIPs(b []byte) (ips []net.IP, err error) {
func extractRemoteHosts(content []byte, rejectIP, rejectDomain bool) (hosts []string) {
lines := strings.Split(string(content), "\n")
for _, line := range lines {
+ line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "remote ") {
continue
}
diff --git a/internal/updater/openvpn/multifetch.go b/internal/updater/openvpn/multifetch.go
index 2d6ccede..9b67c25c 100644
--- a/internal/updater/openvpn/multifetch.go
+++ b/internal/updater/openvpn/multifetch.go
@@ -8,8 +8,8 @@ import (
// FetchMultiFiles fetches multiple Openvpn files in parallel and
// parses them to extract each of their host. A mapping from host to
// URL is returned.
-func FetchMultiFiles(ctx context.Context, client *http.Client, urls []string) (
- hostToURL map[string]string, err error) {
+func FetchMultiFiles(ctx context.Context, client *http.Client, urls []string,
+ failEarly bool) (hostToURL map[string]string, errors []error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -22,14 +22,14 @@ func FetchMultiFiles(ctx context.Context, client *http.Client, urls []string) (
results := make(chan Result)
defer close(results)
- errors := make(chan error)
- defer close(errors)
+ errorsCh := make(chan error)
+ defer close(errorsCh)
for _, url := range urls {
go func(url string) {
host, err := FetchFile(ctx, client, url)
if err != nil {
- errors <- err
+ errorsCh <- err
return
}
results <- Result{
@@ -41,19 +41,26 @@ func FetchMultiFiles(ctx context.Context, client *http.Client, urls []string) (
for range urls {
select {
- case newErr := <-errors:
- if err == nil { // only assign to the first error
- err = newErr
- cancel() // stop other operations, this will trigger other errors we ignore
- }
case result := <-results:
hostToURL[result.host] = result.url
+ case err := <-errorsCh:
+ if !failEarly {
+ errors = append(errors, err)
+ break
+ }
+
+ if len(errors) == 0 {
+ errors = []error{err} // keep only the first error
+ // stop other operations, this will trigger other errors we ignore
+ cancel()
+ }
}
}
- if err != nil {
- return nil, err
+ if len(errors) > 0 && failEarly {
+ // we don't care about the result found
+ return nil, errors
}
- return hostToURL, nil
+ return hostToURL, errors
}