Compare commits

..

13 Commits

Author SHA1 Message Date
Quentin McGaw
90e5742211 Reduce readme size 2020-07-25 11:55:35 -04:00
Quentin McGaw
8f547500d0 Purevpn support (#208)
Fixes #192
2020-07-25 11:19:45 -04:00
Quentin McGaw
0811b8b099 Server filtering fixes for Mullvad and Nordvpn 2020-07-23 02:16:12 +00:00
Quentin McGaw
c5c53a2ff8 FatalOnError fixes 2020-07-23 02:15:37 +00:00
Quentin McGaw
0ce129b63d Make all variables behave like server filters 2020-07-23 01:48:18 +00:00
Quentin McGaw
fec1249293 Uniformize server selection filtering 2020-07-23 01:46:28 +00:00
Quentin McGaw
a5c35455d1 Update PIA IP addresses 2020-07-20 02:32:02 +00:00
Quentin McGaw
28e0abc922 FIREWALL_VPN_INPUT_PORTS variable, fixes #196 2020-07-20 02:07:13 +00:00
Quentin McGaw
a13be8f45e Firewall simplifications
- Only a map of allowed input port to interface
- port forwarded is in the map of allowed input ports
- port forwarded has the interface tun0 in this map
- Always allow tcp and udp for allowed input ports
- Port forward state is in openvpn looper only
- Shadowsocks input port allowed on default interface only
- Tinyproxy input port allowed on default interface only
2020-07-20 00:39:59 +00:00
Quentin McGaw
85bd4f2e8d Get default route and local subnet only at start 2020-07-20 00:35:53 +00:00
Quentin McGaw
4baf0420d6 Openvpn get settings http route 2020-07-19 14:26:24 +00:00
Quentin McGaw
29f74df450 Fix #202 2020-07-19 14:22:23 +00:00
Quentin McGaw
fab9939b26 Simplify DNS loop a bit and fixes #199 2020-07-17 01:16:49 +00:00
47 changed files with 1530 additions and 584 deletions

View File

@@ -42,17 +42,18 @@ ENV VPNSP=pia \
UID=1000 \
GID=1000 \
IP_STATUS_FILE="/ip" \
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN only
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
USER= \
PASSWORD= \
REGION="Austria" \
REGION= \
# PIA only
PIA_ENCRYPTION=strong \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/forwarded_port" \
# Mullvad only
COUNTRY=Sweden \
# Mullvad and PureVPN only
COUNTRY= \
CITY= \
# Mullvad only
ISP= \
# Mullvad and Windscribe only
PORT= \
@@ -82,6 +83,7 @@ ENV VPNSP=pia \
# Firewall
FIREWALL=on \
EXTRA_SUBNETS= \
FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_DEBUG=off \
# Tinyproxy
TINYPROXY=off \
@@ -99,7 +101,7 @@ ENTRYPOINT ["/entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tinyproxy tzdata && \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
echo "http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
apk add -q --progress --no-cache --update shadowsocks-libev && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/tinyproxy/tinyproxy.conf && \
deluser openvpn && \

View File

@@ -1,7 +1,7 @@
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN and NordVPN VPN servers, using Go, OpenVPN,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN,
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
**ANNOUNCEMENT**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
@@ -20,22 +20,11 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
[![Image version](https://images.microbadger.com/badges/version/qmcgaw/private-internet-access.svg)](https://microbadger.com/images/qmcgaw/private-internet-access)
[![Join Slack channel](https://img.shields.io/badge/slack-@qdm12-yellow.svg?logo=slack)](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
<details><summary>Click to show base components</summary><p>
- [Alpine 3.12](https://alpinelinux.org) for a tiny image (37MB of packages, 6.7MB of Go binary and 5.6MB for Alpine)
- [OpenVPN 2.4.9](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/openvpn) to tunnel to your VPN provider servers
- [IPtables 1.8.4](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/iptables) enforces the container to communicate only through the VPN or with other containers in its virtual network (acts as a killswitch)
- [Unbound 1.10.1](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/unbound) configured with Cloudflare's [1.1.1.1](https://1.1.1.1) DNS over TLS (configurable with 5 different providers)
- [Files and blocking lists built periodically](https://github.com/qdm12/updated/tree/master/files) used with Unbound (see `BLOCK_MALICIOUS`, `BLOCK_SURVEILLANCE` and `BLOCK_ADS` environment variables)
- [TinyProxy 1.10.0](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/tinyproxy)
- [Shadowsocks 3.3.4](https://pkgs.alpinelinux.org/package/edge/testing/x86/shadowsocks-libev)
</p></details>
## Features
- Based on Alpine 3.12 for a small Docker image of 52MB
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn** and **NordVPN** servers
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN** and **PureVPN** servers
- Supports Openvpn only for now
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp`
@@ -45,19 +34,7 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
- [Connect other containers to it](https://github.com/qdm12/private-internet-access-docker#connect-to-it)
- [Connect LAN devices to it](https://github.com/qdm12/private-internet-access-docker#connect-to-it)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7 🎆
### VPN provider specifics
- **Private Internet Access**: pick the [region](https://www.privateinternetaccess.com/pages/network/), the level of encryption and enable port forwarding
- **Mullvad**: Pick the [country, city and ISP](https://mullvad.net/en/servers/#openvpn) and optionally a custom port to use (i.e. `53` (udp) or `80` (tcp))
- **Windscribe**: Pick the [region](https://windscribe.com/status), and optionally a custom port to use
- **Surfshark**: Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Surfshark) or a multi hop region name
- **Cyberghost**: Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost) and server group.
- **VyprVPN**: Pick the [region](https://www.vyprvpn.com/server-locations), port forwarding works by default
- **NordVPN**: Pick the region and optionally the server number
### Extra niche features
- VPN server side port forwarding for Private Internet Access and Vyprvpn
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Subprograms all drop root privileges once launched
- Subprograms output streams are all merged together
@@ -66,22 +43,10 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
## Setup
1. Requirements
- A VPN account with one of the service providers:
- Private Internet Access: **username** and **password** ([sign up](https://www.privateinternetaccess.com/pages/buy-vpn/))
- Mullvad: user ID ([sign up](https://mullvad.net/en/account/))
- Windscribe: **username** and **password** | Signup up using my affiliate link below
[![https://windscribe.com/?affid=mh7nyafu](https://raw.githubusercontent.com/qdm12/private-internet-access-docker/master/doc/windscribe.jpg)](https://windscribe.com/?affid=mh7nyafu)
- Surfshark: **username** and **password** ([sign up](https://order.surfshark.com/))
- Cyberghost: **username**, **password** and **device client key file** ([sign up](https://www.cyberghostvpn.com/en_US/buy/cyberghost-vpn-4))
- Vyprvpn: **username** and **password**
- NordVPN: **username** and **password**
- A VPN account with one of the service providers supported
- If you have a host or router firewall, please refer [to the firewall documentation](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/firewall.md)
1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
- *Synology users*: please read [this part of the Wiki](https://github.com/qdm12/private-internet-access-docker/wiki/Common-issues#synology)
1. Launch the container with:
```bash
@@ -125,7 +90,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn` | VPN Service Provider |
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn` | VPN Service Provider |
| `IP_STATUS_FILE` | `/ip` | Any filepath | Filepath to store the public IP address assigned |
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
@@ -134,13 +99,15 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| `OPENVPN_CIPHER` | | i.e. `aes-256-gcm` | Specify a custom cipher to use. It will also set `ncp-disable` if using AES GCM for PIA |
| `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
*For all providers below, server location parameters are all optional. By default a random server is picked using the filter settings provided.*
- Private Internet Access
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | `Austria` | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
| `REGION` | | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
| `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server |
| `PORT_FORWARDING_STATUS_FILE` | `/forwarded_port` | Any filepath | Filepath to store the forwarded port number |
@@ -150,7 +117,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your user ID |
| `COUNTRY` | `Sweden` | One of the [Mullvad countries](https://mullvad.net/en/servers/#openvpn) | VPN server country |
| `COUNTRY` | | One of the [Mullvad countries](https://mullvad.net/en/servers/#openvpn) | VPN server country |
| `CITY` | | One of the [Mullvad cities](https://mullvad.net/en/servers/#openvpn) | VPN server city |
| `ISP` | | One of the [Mullvad ISP](https://mullvad.net/en/servers/#openvpn) | VPN server ISP |
| `PORT` | | `80` or `443` for TCP; or `53` for UDP. Leave blank for default Mullvad server port | Custom VPN port to use |
@@ -161,7 +128,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | `Austria` | One of the [Windscribe regions](https://windscribe.com/status) | VPN server region |
| `REGION` | | One of the [Windscribe regions](https://windscribe.com/status) | VPN server region |
| `PORT` | | One from the [this list of ports](https://windscribe.com/getconfig/openvpn) | Custom VPN port to use |
- Surfshark
@@ -170,7 +137,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your **service** username, found at the bottom of the [manual setup page](https://account.surfshark.com/setup/manual) |
| 🏁 `PASSWORD` | | | Your **service** password |
| `REGION` | `Austria` | One of the [Surfshark regions (subdomains)](https://github.com/qdm12/private-internet-access-docker/wiki/surfshark) | VPN server region |
| `REGION` | | One of the [Surfshark regions](https://github.com/qdm12/private-internet-access-docker/wiki/surfshark) | VPN server region |
- Cyberghost
@@ -179,7 +146,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| 🏁 `CLIENT_KEY` | | | Your device client key content, **see below** |
| `REGION` | `Austria` | One of the [Cyberghost countries](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost#regions) | VPN server country |
| `REGION` | | One of the [Cyberghost countries](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost#regions) | VPN server country |
| `CYBERGHOST_GROUP` | `Premium UDP Europe` | One of the [server groups](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost#server-groups) | Server group |
To specify your client key, you can either:
@@ -199,7 +166,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
| `REGION` | | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
- NordVPN
@@ -207,9 +174,18 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| 🏁 `REGION` | `Austria` (wrong) | One of the NordVPN server country, i.e. `Switzerland` | VPN server country |
| `REGION` | | One of the NordVPN server country, i.e. `Switzerland` | VPN server country |
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
- PureVPN
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your user ID |
| 🏁 `REGION` | | One of the [PureVPN regions](https://support.purevpn.com/vpn-servers) | VPN server region |
| `COUNTRY` | | One of the [PureVPN countries](https://support.purevpn.com/vpn-servers) | VPN server country |
| `CITY` | | One of the [PureVPN cities](https://support.purevpn.com/vpn-servers) | VPN server city |
### DNS over TLS
None of the following values are required.
@@ -240,6 +216,7 @@ That one is important if you want to connect to the container from your LAN for
| --- | --- | --- | --- |
| `FIREWALL` | `on` | `on` or `off` | Turn on or off the container built-in firewall. You should use it for **debugging purposes** only. |
| `EXTRA_SUBNETS` | | i.e. `192.168.1.0/24,192.168.10.121,10.0.0.5/28` | Comma separated subnets allowed in the container firewall |
| `FIREWALL_VPN_INPUT_PORTS` | | i.e. `1000,8080` | Comma separated list of ports to allow from the VPN server side (useful for **vyprvpn** port forwarding) |
| `FIREWALL_DEBUG` | `off` | `on` or `off` | Prints every firewall related command. You should use it for **debugging purposes** only. |
### Shadowsocks
@@ -368,15 +345,13 @@ There are various ways to achieve this, depending on your use case.
Note that [not all regions support port forwarding](https://www.privateinternetaccess.com/helpdesk/kb/articles/how-do-i-enable-port-forwarding-on-my-vpn).
When `PORT_FORWARDING=on`, a port will be forwarded on the VPN server side and written to the file specified by `PORT_FORWARDING_STATUS_FILE=/forwarded_port`.
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
You can also use the HTTP control server (see below) to get the port forwarded.
## HTTP control server
A built-in HTTP server listens on port `8000` to modify the state of the container. You have the following routes available:
- `http://<your-docker-host-ip>:8000/openvpn/actions/restart` restarts the openvpn process
- `http://<your-docker-host-ip>:8000/unbound/actions/restart` re-downloads the DNS files (crypto and block lists) and restarts the unbound process
See [its Wiki page](https://github.com/qdm12/private-internet-access-docker/wiki/HTTP-control-server)
## Development and contributing

View File

@@ -16,6 +16,7 @@ import (
"github.com/qdm12/golibs/network"
"github.com/qdm12/private-internet-access-docker/internal/alpine"
"github.com/qdm12/private-internet-access-docker/internal/cli"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/dns"
"github.com/qdm12/private-internet-access-docker/internal/firewall"
gluetunLogging "github.com/qdm12/private-internet-access-docker/internal/logging"
@@ -56,8 +57,8 @@ func _main(background context.Context, args []string) int {
ctx, cancel := context.WithCancel(background)
defer cancel()
logger := createLogger()
wg := &sync.WaitGroup{}
fatalOnError := makeFatalOnError(logger, cancel, wg)
fatalOnError := makeFatalOnError(logger, cancel)
client := network.NewClient(15 * time.Second)
// Create configurators
@@ -104,6 +105,18 @@ func _main(background context.Context, args []string) int {
routingConf.SetDebug()
}
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil {
fatalOnError(err)
}
localSubnet, err := routingConf.LocalSubnet()
if err != nil {
fatalOnError(err)
}
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet)
if err := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
@@ -125,10 +138,19 @@ func _main(background context.Context, args []string) int {
err = firewallConf.SetAllowedSubnets(ctx, allSettings.Firewall.AllowedSubnets)
fatalOnError(err)
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
fatalOnError(err)
}
wg := &sync.WaitGroup{}
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid,
ovpnConf, firewallConf, logger, client, fileManager, streamMerger, fatalOnError)
restartOpenvpn := openvpnLooper.Restart
portForward := openvpnLooper.PortForward
getOpenvpnSettings := openvpnLooper.GetSettings
getPortForwarded := openvpnLooper.GetPortForwarded
// wait for restartOpenvpn
go openvpnLooper.Run(ctx, wg)
@@ -144,11 +166,11 @@ func _main(background context.Context, args []string) int {
go publicIPLooper.RunRestartTicker(ctx)
setPublicIPPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid)
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid, defaultInterface)
restartTinyproxy := tinyproxyLooper.Restart
go tinyproxyLooper.Run(ctx, wg)
shadowsocksLooper := shadowsocks.NewLooper(shadowsocksConf, firewallConf, allSettings.ShadowSocks, allSettings.DNS, logger, streamMerger, uid, gid)
shadowsocksLooper := shadowsocks.NewLooper(shadowsocksConf, firewallConf, allSettings.ShadowSocks, allSettings.DNS, logger, streamMerger, uid, gid, defaultInterface)
restartShadowsocks := shadowsocksLooper.Restart
go shadowsocksLooper.Run(ctx, wg)
@@ -176,7 +198,7 @@ func _main(background context.Context, args []string) int {
}
}()
httpServer := server.New("0.0.0.0:8000", logger, restartOpenvpn, restartUnbound)
httpServer := server.New("0.0.0.0:8000", logger, restartOpenvpn, restartUnbound, getOpenvpnSettings, getPortForwarded)
go httpServer.Run(ctx, wg)
// Start openvpn for the first time
@@ -229,18 +251,11 @@ func _main(background context.Context, args []string) int {
return 0
}
func makeFatalOnError(logger logging.Logger, cancel context.CancelFunc, wg *sync.WaitGroup) func(err error) {
func makeFatalOnError(logger logging.Logger, cancel context.CancelFunc) func(err error) {
return func(err error) {
if err != nil {
logger.Error(err)
cancel()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
go func() {
wg.Wait()
cancel()
}()
<-ctx.Done() // either timeout or wait group completed
os.Exit(1)
}
}
}

View File

@@ -0,0 +1,111 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/qdm12/golibs/network"
)
func main() {
os.Exit(_main())
}
func _main() int {
provider := flag.String("provider", "purevpn", "VPN provider to map location to subdomain, can be 'purevpn'")
flag.Parse()
client := network.NewClient(5 * time.Second)
switch *provider {
case "purevpn":
servers, warnings, err := purevpn(client)
if err != nil {
fmt.Println(err)
return 1
}
for _, server := range servers {
fmt.Printf(
"{subdomain: %q, region: %q, country: %q, city: %q},\n",
server.subdomain, server.region, server.country, server.city,
)
}
fmt.Print("\n\n")
for _, warning := range warnings {
fmt.Println(warning)
}
default:
fmt.Printf("Provider %q is not supported\n", *provider)
return 1
}
return 0
}
type purevpnServer struct {
region string
country string
city string
subdomain string // without -tcp or -udp suffix
}
func purevpn(client network.Client) (servers []purevpnServer, warnings []string, err error) {
content, status, err := client.GetContent("https://support.purevpn.com/vpn-servers")
if err != nil {
return nil, nil, err
} else if status != http.StatusOK {
return nil, nil, fmt.Errorf("HTTP status %d from Purevpn", status)
}
const jsonPrefix = "<script>var servers = "
const jsonSuffix = "</script>"
s := string(content)
jsonPrefixIndex := strings.Index(s, jsonPrefix)
if jsonPrefixIndex == -1 {
return nil, nil, fmt.Errorf("cannot find prefix %s in html", jsonPrefix)
}
if len(s[jsonPrefixIndex:]) == len(jsonPrefix) {
return nil, nil, fmt.Errorf("no body after json prefix %s", jsonPrefix)
}
s = s[jsonPrefixIndex+len(jsonPrefix):]
endIndex := strings.Index(s, jsonSuffix)
s = s[:endIndex]
var data []struct {
Region string `json:"region_name"`
Country string `json:"country_name"`
City string `json:"city_name"`
TCP string `json:"tcp"`
UDP string `json:"udp"`
}
if err := json.Unmarshal([]byte(s), &data); err != nil {
return nil, nil, err
}
sort.Slice(data, func(i, j int) bool {
if data[i].Region == data[j].Region {
if data[i].Country == data[j].Country {
return data[i].City < data[j].City
}
return data[i].Country < data[j].Country
}
return data[i].Region < data[j].Region
})
for i := range data {
if data[i].UDP == "" && data[i].TCP == "" {
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP and UDP for openvpn", data[i].Region, data[i].Country, data[i].City))
continue
}
if data[i].UDP == "" || data[i].TCP == "" {
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP or udp for openvpn", data[i].Region, data[i].Country, data[i].City))
}
servers = append(servers, purevpnServer{
region: data[i].Region,
country: data[i].Country,
city: data[i].City,
subdomain: strings.TrimSuffix(data[i].TCP, "-tcp.pointtoserver.com"),
})
}
return servers, warnings, nil
}

View File

@@ -18,7 +18,7 @@ func main() {
func _main(ctx context.Context) int {
resolverAddress := flag.String("resolver", "1.1.1.1", "DNS Resolver IP address to use")
provider := flag.String("provider", "pia", "VPN provider to resolve for, 'pia', 'windscribe', 'cyberghost' or 'vyprvpn'")
provider := flag.String("provider", "pia", "VPN provider to resolve for, 'pia', 'windscribe', 'cyberghost', 'vyprvpn' or 'purevpn'")
region := flag.String("region", "all", "Comma separated list of VPN provider region names to resolve for, use 'all' to resolve all")
flag.Parse()
@@ -43,6 +43,9 @@ func _main(ctx context.Context) int {
case "vyprvpn":
domain = "vyprvpn.com"
servers = vyprvpnServers()
case "purevpn":
domain = "pointtoserver.com"
servers = purevpnServers()
default:
fmt.Printf("Provider %q is not supported\n", *provider)
return 1
@@ -135,6 +138,11 @@ func formatLine(provider string, s server, ips []net.IP) string {
"{Region: %q, IPs: []net.IP{%s}},",
s.region, ipString,
)
case "purevpn":
return fmt.Sprintf(
"{Region: %q, Country: %q, City: %q, IPs: []net.IP{%s}},",
s.region, s.country, s.city, ipString,
)
}
return ""
}
@@ -197,6 +205,8 @@ type server struct {
subdomain string
region string
group string // only for cyberghost
country string // only for purevpn
city string // only for purevpn
}
func piaServers() []server {
@@ -747,3 +757,164 @@ func vyprvpnServers() []server {
{subdomain: "vn1", region: "Vietnam"},
}
}
func purevpnServers() []server {
servers := []server{
{subdomain: "vlus-dz1-ovpn", region: "Africa", country: "Algeria", city: "Algiers"},
{subdomain: "vlus-ao1-ovpn", region: "Africa", country: "Angola", city: "Benguela"},
{subdomain: "vleu-cv-ovpn", region: "Africa", country: "Cape Verde", city: "Praia"},
{subdomain: "vlus-eg1-ovpn", region: "Africa", country: "Egypt", city: "Cairo"},
{subdomain: "et1-ovpn", region: "Africa", country: "Ethiopia", city: "Addis Ababa"},
{subdomain: "gh1-ovpn", region: "Africa", country: "Ghana", city: "Accra"},
{subdomain: "ke1-ovpn", region: "Africa", country: "Kenya", city: "Mombasa"},
{subdomain: "vlus-mg1-ovpn", region: "Africa", country: "Madagascar", city: "Antananarivo"},
{subdomain: "vlus-mr1-ovpn", region: "Africa", country: "Mauritania", city: "Nouakchott"},
{subdomain: "mu1-ovpn", region: "Africa", country: "Mauritius", city: "Port Louis"},
{subdomain: "ma1-ovpn", region: "Africa", country: "Morocco", city: "Rabat"},
{subdomain: "vlus-ne1-ovpn", region: "Africa", country: "Niger", city: "Niamey"},
{subdomain: "ng1-ovpn", region: "Africa", country: "Nigeria", city: "Suleja"},
{subdomain: "vlus-sn1-ovpn", region: "Africa", country: "Senegal", city: "Dakar"},
{subdomain: "sc1-ovpn", region: "Africa", country: "Seychelles", city: "Victoria"},
{subdomain: "za2-ovpn", region: "Africa", country: "South Africa", city: "Johannesburg"},
{subdomain: "vlus-tz1-ovpn", region: "Africa", country: "Tanzania", city: "Dar Es Salaam"},
{subdomain: "vlus-tn1-ovpn", region: "Africa", country: "Tunisia", city: "Tunis"},
{subdomain: "vlus-af1-ovpn", region: "Asia", country: "Afghanistan", city: "Kabul"},
{subdomain: "sg2-ovpn", region: "Asia", country: "Armenia", city: "Singapore"},
{subdomain: "az1-ovpn", region: "Asia", country: "Azerbaijan", city: "Baku"},
{subdomain: "vlus-bd1-ovpn", region: "Asia", country: "Bangladesh", city: "Dhaka"},
{subdomain: "bn2-ovpn", region: "Asia", country: "Brunei Darussalam", city: "Bandar Seri Begawan"},
{subdomain: "kh1-ovpn", region: "Asia", country: "Cambodia", city: "Phnom Penh"},
{subdomain: "hk2-ovpn", region: "Asia", country: "Hong Kong (SAR)", city: "Hong Kong"},
{subdomain: "in2-ovpn", region: "Asia", country: "India", city: "Chennai"},
{subdomain: "idn1-ovpn", region: "Asia", country: "Indonesia", city: "Jakarta"},
{subdomain: "jp-tk1-ovpn", region: "Asia", country: "Japan", city: "Tokyo"},
{subdomain: "vlus-kz1-ovpn", region: "Asia", country: "Kazakhstan", city: "Almaty"},
{subdomain: "kr2-ovpn", region: "Asia", country: "Korea, South", city: "Seoul"},
{subdomain: "vlus-kg1-ovpn", region: "Asia", country: "Kyrgyzstan", city: "Bishkek"},
{subdomain: "vlus-la1-ovpn", region: "Asia", country: "Laos", city: "Vientiane"},
{subdomain: "mo1-ovpn", region: "Asia", country: "Macao", city: "Beyrouth"},
{subdomain: "my2-ovpn", region: "Asia", country: "Malaysia", city: "Johor Baharu"},
{subdomain: "my-kl2-ovpn", region: "Asia", country: "Malaysia", city: "Kuala Lumpur"},
{subdomain: "vlus-mn1-ovpn", region: "Asia", country: "Mongolia", city: "Ulaanbaatar"},
{subdomain: "pk1-ovpn", region: "Asia", country: "Pakistan", city: "Islamabad"},
{subdomain: "vlus-pg1-ovpn", region: "Asia", country: "Papua New Guinea", city: "Port Moresby"},
{subdomain: "vlap-ph2-ovpn", region: "Asia", country: "Philippines", city: "Manila"},
{subdomain: "vlus-lk1-ovpn", region: "Asia", country: "Sri Lanka", city: "Colombo"},
{subdomain: "tw2-ovpn", region: "Asia", country: "Taiwan", city: "Taipei"},
{subdomain: "vlus-tj-ovpn", region: "Asia", country: "Tajikistan", city: "Dushanbe"},
{subdomain: "vlap-th2-ovpn", region: "Asia", country: "Thailand", city: "Bangkok"},
{subdomain: "tr2-ovpn", region: "Asia", country: "Turkey", city: "Istanbul"},
{subdomain: "vlus-tm1-ovpn", region: "Asia", country: "Turkmenistan", city: "Ashgabat"},
{subdomain: "vlus-uz-ovpn", region: "Asia", country: "Uzbekistan", city: "Tashkent"},
{subdomain: "vlap-vn2-ovpn", region: "Asia", country: "Vietnam", city: "Hanoi"},
{subdomain: "al1-ovpn", region: "Europe", country: "Albania", city: "Tirane"},
{subdomain: "vleu-am1-ovpn", region: "Europe", country: "Armenia", city: "Yerevan"},
{subdomain: "at2-ovpn", region: "Europe", country: "Austria", city: "Vienna"},
{subdomain: "vleu-be2-ovpn", region: "Europe", country: "Belgium", city: "Brussels"},
{subdomain: "ba1-ovpn", region: "Europe", country: "Bosnia and Herzegovina", city: "Sarajevo"},
{subdomain: "bg2-ovpn", region: "Europe", country: "Bulgaria", city: "Sofia"},
{subdomain: "vlus-hr1-ovpn", region: "Europe", country: "Croatia", city: "Zagreb"},
{subdomain: "cy1-ovpn", region: "Europe", country: "Cyprus", city: "Nicosia"},
{subdomain: "dk2-ovpn", region: "Europe", country: "Denmark", city: "Copenhagen"},
{subdomain: "ee1-ovpn", region: "Europe", country: "Estonia", city: "Tallinn"},
{subdomain: "fr2-ovpn", region: "Europe", country: "France", city: "Paris"},
{subdomain: "vlus-ge1-ovpn", region: "Europe", country: "Georgia", city: "Tbilisi"},
{subdomain: "de2-ovpn", region: "Europe", country: "Germany", city: "Frankfurt"},
{subdomain: "de2-ovpn", region: "Europe", country: "Germany", city: "Munich"},
{subdomain: "de-ao1-ovpn", region: "Europe", country: "Germany", city: "Nuremberg"},
{subdomain: "gr2-ovpn", region: "Europe", country: "Greece", city: "Thessaloniki"},
{subdomain: "hu2-ovpn", region: "Europe", country: "Hungary", city: "Budapest"},
{subdomain: "is1-ovpn", region: "Europe", country: "Iceland", city: "Reykjavik"},
{subdomain: "ie2-ovpn", region: "Europe", country: "Ireland", city: "Dublin"},
{subdomain: "im1-ovpn", region: "Europe", country: "Isle of Man", city: "Onchan"},
{subdomain: "vlus-it1-ovpn", region: "Europe", country: "Italy", city: "Milano"},
{subdomain: "lv1-ovpn", region: "Europe", country: "Latvia", city: "RIGA"},
{subdomain: "li1-ovpn", region: "Europe", country: "Liechtenstein", city: "Vaduz"},
{subdomain: "lt1-ovpn", region: "Europe", country: "Lithuania", city: "Vilnius"},
{subdomain: "lu2-ovpn", region: "Europe", country: "Luxembourg", city: "Luxembourg"},
{subdomain: "mt1-ovpn", region: "Europe", country: "Malta", city: "Sliema"},
{subdomain: "mn1-ovpn", region: "Europe", country: "Monaco", city: "Monaco"},
{subdomain: "vleu-me1-ovpn", region: "Europe", country: "Montenegro", city: "Podgorica"},
{subdomain: "nl2-ovpn", region: "Europe", country: "Netherlands", city: "Amsterdam"},
{subdomain: "vleu-no2-ovpn", region: "Europe", country: "Norway", city: "Oslo"},
{subdomain: "pl2-ovpn", region: "Europe", country: "Poland", city: "Warsaw"},
{subdomain: "pt2-ovpn", region: "Europe", country: "Portugal", city: "Lisbon"},
{subdomain: "ro2-ovpn", region: "Europe", country: "Romania", city: "Bucharest"},
{subdomain: "rs2-ovpn", region: "Europe", country: "Serbia", city: "Niš"},
{subdomain: "sk1-ovpn", region: "Europe", country: "Slovakia", city: "Bratislava"},
{subdomain: "si1-ovpn", region: "Europe", country: "Slovenia", city: "Ljubljana"},
{subdomain: "es-ovpn", region: "Europe", country: "Spain", city: "Barcelona"},
{subdomain: "vlus-se1-ovpn", region: "Europe", country: "Sweden", city: "Stockholm"},
{subdomain: "ch2-ovpn", region: "Europe", country: "Switzerland", city: "Zurich"},
{subdomain: "ukg2-ovpn", region: "Europe", country: "United Kingdom", city: "Gosport"},
{subdomain: "ukl2-ovpn", region: "Europe", country: "United Kingdom", city: "London"},
{subdomain: "ukm2-ovpn", region: "Europe", country: "United Kingdom", city: "Maidenhead"},
{subdomain: "vlus-uk-man1-ovpn", region: "Europe", country: "United Kingdom", city: "Manchester"},
{subdomain: "bh-ovpn", region: "Middle East", country: "Bahrain", city: "Manama"},
{subdomain: "vlus-jo1-ovpn", region: "Middle East", country: "Jordan", city: "Amman"},
{subdomain: "vlus-kw1-ovpn", region: "Middle East", country: "Kuwait", city: "Kuwait"},
{subdomain: "om1-ovpn", region: "Middle East", country: "Oman", city: "Salalah"},
{subdomain: "qa1-ovpn", region: "Middle East", country: "Qatar", city: "Doha"},
{subdomain: "sa1-ovpn", region: "Middle East", country: "Saudi Arabia", city: "Jeddah"},
{subdomain: "ae2-ovpn", region: "Middle East", country: "United Arab Emirates", city: "Dubai"},
{subdomain: "aw1-ovpn", region: "North America", country: "Aruba", city: "Oranjestad"},
{subdomain: "vleu-bb-ovpn", region: "North America", country: "Barbados", city: "Bridgetown"},
{subdomain: "bz1-ovpn", region: "North America", country: "Belize", city: "Belmopan"},
{subdomain: "vleu-bm-ovpn", region: "North America", country: "Bermuda", city: "Hamilton"},
{subdomain: "caq1-ovpn", region: "North America", country: "Canada", city: "Montreal"},
{subdomain: "cato-ovpn", region: "North America", country: "Canada", city: "Toronto"},
{subdomain: "cav2-ovpn", region: "North America", country: "Canada", city: "Vancouver"},
{subdomain: "vleu-ky-ovpn", region: "North America", country: "Cayman Islands", city: "George Town"},
{subdomain: "vlus-cr1-ovpn", region: "North America", country: "Costa Rica", city: "San Jose"},
{subdomain: "vleu-dm-ovpn", region: "North America", country: "Dominica", city: "Roseau"},
{subdomain: "vleu-do-ovpn", region: "North America", country: "Dominican Republic", city: "Santo Domingo"},
{subdomain: "vleu-sv-ovpn", region: "North America", country: "El Salvador", city: "San Salvador"},
{subdomain: "vleu-gd-ovpn", region: "North America", country: "Grenada", city: "St George's"},
{subdomain: "vleu-gt-ovpn", region: "North America", country: "Guatemala", city: "Guatemala"},
{subdomain: "vleu-ht1-ovpn", region: "North America", country: "Haiti", city: "PORT-AU-PRINCE"},
{subdomain: "vleu-hn-ovpn", region: "North America", country: "Honduras", city: "TEGUCIGALPA"},
{subdomain: "jm1-ovpn", region: "North America", country: "Jamaica", city: "Kingston"},
{subdomain: "vlus-mx2-ovpn", region: "North America", country: "Mexico", city: "Mexico City"},
{subdomain: "vleu-ms-ovpn", region: "North America", country: "Montserrat", city: "plymouth"},
{subdomain: "pr1-ovpn", region: "North America", country: "Puerto Rico", city: "San Juan"},
{subdomain: "vleu-lc-ovpn", region: "North America", country: "Saint Lucia", city: "Castries"},
{subdomain: "bs1-ovpn", region: "North America", country: "The Bahamas", city: "Freeport"},
{subdomain: "vleu-tt-ovpn", region: "North America", country: "Trinidad and Tobago", city: "Port of Spain"},
{subdomain: "vleu-tc-ovpn", region: "North America", country: "Turks and Caicos Islands", city: "Balfour Town"},
{subdomain: "usva-ovpn", region: "North America", country: "United States", city: "Ashburn"},
{subdomain: "usil2-ovpn", region: "North America", country: "United States", city: "Chicago"},
{subdomain: "usoh1-ovpn", region: "North America", country: "United States", city: "Columbus"},
{subdomain: "usga2-ovpn", region: "North America", country: "United States", city: "Georgia"},
{subdomain: "ustx2-ovpn", region: "North America", country: "United States", city: "Houston"},
{subdomain: "usla2-ovpn", region: "North America", country: "United States", city: "Los Angeles"},
{subdomain: "usfl2-ovpn", region: "North America", country: "United States", city: "Miami"},
{subdomain: "usnj2-ovpn", region: "North America", country: "United States", city: "New Jersey"},
{subdomain: "usny2-ovpn", region: "North America", country: "United States", city: "New York"},
{subdomain: "usphx2-ovpn", region: "North America", country: "United States", city: "Phoenix"},
{subdomain: "usut2-ovpn", region: "North America", country: "United States", city: "Salt Lake City"},
{subdomain: "ussf2-ovpn", region: "North America", country: "United States", city: "San Francisco"},
{subdomain: "ussa-ovpn", region: "North America", country: "United States", city: "Seattle"},
{subdomain: "uswdc2-ovpn", region: "North America", country: "United States", city: "Washington, D.C."},
{subdomain: "au-bn-ovpn", region: "Oceania", country: "Australia", city: "Brisbane"},
{subdomain: "au-me1-ovpn", region: "Oceania", country: "Australia", city: "Melbourne"},
{subdomain: "au2-pe-ovpn", region: "Oceania", country: "Australia", city: "Perth"},
{subdomain: "au-sd2-ovpn", region: "Oceania", country: "Australia", city: "Sydney"},
{subdomain: "nz2-ovpn", region: "Oceania", country: "New Zealand", city: "Auckland"},
{subdomain: "vlus-ar1-ovpn", region: "South America", country: "Argentina", city: "Buenos Aires"},
{subdomain: "vleu-bo-ovpn", region: "South America", country: "Bolivia", city: "Sucre"},
{subdomain: "br2-ovpn", region: "South America", country: "Brazil", city: "Sao Paulo"},
{subdomain: "vg1-ovpn", region: "South America", country: "British Virgin Island", city: "Road Town"},
{subdomain: "vlbr-cl-ovpn", region: "South America", country: "Chile", city: "Santiago"},
{subdomain: "co1-ovpn", region: "South America", country: "Colombia", city: "Bogota"},
{subdomain: "ec1-ovpn", region: "South America", country: "Ecuador", city: "Quito"},
{subdomain: "vleu-gy-ovpn", region: "South America", country: "Guyana", city: "Georgetown"},
{subdomain: "pa2-ovpn", region: "South America", country: "Panama", city: "Panama City"},
{subdomain: "vleu-py-ovpn", region: "South America", country: "Paraguay", city: "Asuncion"},
{subdomain: "pe1-ovpn", region: "South America", country: "Peru", city: "Lima"},
{subdomain: "vleu-sr-ovpn", region: "South America", country: "Suriname", city: "Paramaribo"},
}
for i := range servers {
servers[i].subdomain += "-udp"
}
return servers
}

View File

@@ -13,7 +13,7 @@ const (
func MullvadCountryChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range mullvadServers() {
for _, server := range MullvadServers() {
uniqueChoices[server.Country] = struct{}{}
}
for choice := range uniqueChoices {
@@ -27,7 +27,7 @@ func MullvadCountryChoices() (choices []string) {
func MullvadCityChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range mullvadServers() {
for _, server := range MullvadServers() {
uniqueChoices[server.City] = struct{}{}
}
for choice := range uniqueChoices {
@@ -41,7 +41,7 @@ func MullvadCityChoices() (choices []string) {
func MullvadISPChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range mullvadServers() {
for _, server := range MullvadServers() {
uniqueChoices[server.ISP] = struct{}{}
}
for choice := range uniqueChoices {
@@ -53,25 +53,7 @@ func MullvadISPChoices() (choices []string) {
return choices
}
func MullvadServerFilter(country, city, isp string) (servers []models.MullvadServer) {
for _, server := range mullvadServers() {
if len(country) == 0 {
server.Country = ""
}
if len(city) == 0 {
server.City = ""
}
if len(isp) == 0 {
server.ISP = ""
}
if server.Country == country && server.City == city && server.ISP == isp {
servers = append(servers, server)
}
}
return servers
}
func mullvadServers() []models.MullvadServer {
func MullvadServers() []models.MullvadServer {
return []models.MullvadServer{
{
Country: "united arab emirates",

View File

@@ -26,56 +26,56 @@ func PIAGeoChoices() (choices []string) {
func PIAServers() []models.PIAServer {
return []models.PIAServer{
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {27, 50, 82, 133}, {43, 250, 204, 81}, {43, 250, 204, 83}, {43, 250, 204, 87}, {43, 250, 204, 89}, {43, 250, 204, 91}, {43, 250, 204, 93}, {43, 250, 204, 97}, {43, 250, 204, 99}, {43, 250, 204, 101}, {43, 250, 204, 103}, {43, 250, 204, 107}, {43, 250, 204, 111}, {43, 250, 204, 117}, {43, 250, 204, 119}, {43, 250, 204, 123}, {43, 250, 204, 125}, {118, 127, 62, 227}, {221, 121, 139, 175}}},
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 89}, {43, 250, 205, 91}, {43, 250, 205, 93}, {43, 250, 205, 95}}},
{Region: "AU Sydney", IPs: []net.IP{{27, 50, 77, 247}, {103, 13, 102, 113}, {103, 13, 102, 115}, {103, 13, 102, 117}, {103, 13, 102, 119}, {103, 13, 102, 121}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 53}, {118, 127, 60, 61}, {221, 121, 145, 131}, {221, 121, 145, 133}, {221, 121, 145, 143}, {221, 121, 145, 151}, {221, 121, 146, 203}, {221, 121, 152, 215}}},
{Region: "Austria", IPs: []net.IP{{185, 210, 219, 147}, {185, 210, 219, 154}, {185, 210, 219, 156}, {185, 216, 34, 226}, {185, 216, 34, 228}, {185, 216, 34, 229}, {185, 216, 34, 230}, {185, 216, 34, 232}, {185, 216, 34, 233}, {185, 216, 34, 236}, {185, 216, 34, 237}, {185, 216, 34, 238}}},
{Region: "Belgium", IPs: []net.IP{{77, 243, 191, 19}, {77, 243, 191, 21}, {77, 243, 191, 22}, {77, 243, 191, 23}, {77, 243, 191, 26}, {77, 243, 191, 27}, {185, 104, 186, 26}, {185, 232, 21, 26}, {185, 232, 21, 29}}},
{Region: "CA Montreal", IPs: []net.IP{{199, 229, 249, 142}}},
{Region: "CA Toronto", IPs: []net.IP{{172, 98, 67, 37}, {172, 98, 67, 41}, {172, 98, 67, 45}, {172, 98, 67, 47}, {172, 98, 67, 48}, {172, 98, 67, 55}, {172, 98, 67, 56}, {172, 98, 67, 61}, {172, 98, 67, 64}, {172, 98, 67, 65}, {172, 98, 67, 68}, {172, 98, 67, 71}, {172, 98, 67, 73}, {172, 98, 67, 81}, {172, 98, 67, 85}, {172, 98, 67, 89}, {172, 98, 67, 91}, {172, 98, 67, 93}, {172, 98, 67, 95}, {172, 98, 67, 99}}},
{Region: "CA Vancouver", IPs: []net.IP{{172, 83, 40, 107}}},
{Region: "Czech Republic", IPs: []net.IP{{89, 238, 186, 226}, {89, 238, 186, 227}, {89, 238, 186, 228}, {89, 238, 186, 229}, {89, 238, 186, 230}, {185, 216, 35, 66}, {185, 216, 35, 67}, {185, 216, 35, 69}, {185, 216, 35, 70}, {185, 242, 6, 27}, {185, 242, 6, 28}, {185, 242, 6, 29}, {185, 242, 6, 30}}},
{Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 227}, {185, 230, 127, 228}, {185, 230, 127, 231}, {185, 230, 127, 232}, {185, 230, 127, 233}, {185, 230, 127, 234}, {185, 230, 127, 236}, {185, 230, 127, 239}, {185, 230, 127, 240}, {185, 230, 127, 241}, {193, 176, 86, 124}, {193, 176, 86, 130}, {193, 176, 86, 142}, {193, 176, 86, 146}, {193, 176, 86, 150}, {193, 176, 86, 154}, {193, 176, 86, 166}, {193, 176, 86, 174}, {193, 176, 86, 178}, {193, 176, 86, 182}}},
{Region: "DE Frankfurt", IPs: []net.IP{{185, 220, 70, 134}, {185, 220, 70, 135}, {185, 220, 70, 136}, {185, 220, 70, 139}, {185, 220, 70, 140}, {185, 220, 70, 141}, {185, 220, 70, 143}, {185, 220, 70, 144}, {185, 220, 70, 145}, {185, 220, 70, 147}, {185, 220, 70, 148}, {185, 220, 70, 152}, {185, 220, 70, 153}, {185, 220, 70, 155}, {185, 220, 70, 163}, {185, 220, 70, 164}, {185, 220, 70, 167}, {185, 220, 70, 170}, {185, 220, 70, 171}, {185, 220, 70, 173}}},
{Region: "Denmark", IPs: []net.IP{{82, 102, 20, 162}, {82, 102, 20, 163}, {82, 102, 20, 164}, {82, 102, 20, 165}, {82, 102, 20, 167}, {82, 102, 20, 168}, {82, 102, 20, 169}, {82, 102, 20, 170}, {82, 102, 20, 171}, {82, 102, 20, 172}, {82, 102, 20, 173}, {82, 102, 20, 174}, {82, 102, 20, 175}, {82, 102, 20, 178}, {82, 102, 20, 179}, {82, 102, 20, 180}, {82, 102, 20, 181}, {82, 102, 20, 182}, {82, 102, 20, 183}}},
{Region: "Finlan", IPs: []net.IP{{196, 244, 191, 2}, {196, 244, 191, 10}, {196, 244, 191, 42}, {196, 244, 191, 50}, {196, 244, 191, 58}, {196, 244, 191, 66}, {196, 244, 191, 74}, {196, 244, 191, 82}, {196, 244, 191, 90}, {196, 244, 191, 98}, {196, 244, 191, 106}, {196, 244, 191, 114}, {196, 244, 191, 138}, {196, 244, 191, 146}}},
{Region: "France", IPs: []net.IP{{194, 187, 249, 35}, {194, 187, 249, 37}, {194, 187, 249, 39}, {194, 187, 249, 42}, {194, 187, 249, 45}, {194, 187, 249, 48}, {194, 187, 249, 49}, {194, 187, 249, 52}, {194, 187, 249, 53}, {194, 187, 249, 56}, {194, 187, 249, 59}, {194, 187, 249, 60}, {194, 187, 249, 61}, {194, 187, 249, 178}, {194, 187, 249, 179}, {194, 187, 249, 180}, {194, 187, 249, 183}, {194, 187, 249, 184}, {194, 187, 249, 187}, {194, 187, 249, 190}}},
{Region: "Hong Kong", IPs: []net.IP{{84, 17, 37, 1}, {84, 17, 37, 45}, {119, 81, 135, 2}, {119, 81, 135, 28}, {119, 81, 135, 29}, {119, 81, 135, 47}, {119, 81, 135, 51}, {119, 81, 135, 53}, {119, 81, 253, 212}, {119, 81, 253, 218}, {119, 81, 253, 226}, {119, 81, 253, 227}, {119, 81, 253, 229}, {119, 81, 253, 230}, {119, 81, 253, 241}, {161, 202, 39, 202}, {161, 202, 39, 240}, {161, 202, 39, 251}, {161, 202, 44, 94}}},
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {27, 50, 82, 133}, {43, 250, 204, 83}, {43, 250, 204, 87}, {43, 250, 204, 89}, {43, 250, 204, 91}, {43, 250, 204, 93}, {43, 250, 204, 95}, {43, 250, 204, 97}, {43, 250, 204, 101}, {43, 250, 204, 103}, {43, 250, 204, 105}, {43, 250, 204, 107}, {43, 250, 204, 109}, {43, 250, 204, 111}, {43, 250, 204, 113}, {43, 250, 204, 115}, {43, 250, 204, 117}, {43, 250, 204, 123}, {43, 250, 204, 125}, {118, 127, 62, 227}, {221, 121, 139, 175}}},
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 91}, {43, 250, 205, 95}}},
{Region: "AU Sydney", IPs: []net.IP{{103, 13, 102, 113}, {103, 13, 102, 115}, {103, 13, 102, 117}, {103, 13, 102, 119}, {103, 13, 102, 121}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 53}, {118, 127, 60, 61}, {221, 121, 145, 133}, {221, 121, 145, 143}, {221, 121, 146, 203}, {221, 121, 152, 215}}},
{Region: "Austria", IPs: []net.IP{{185, 210, 219, 147}, {185, 210, 219, 154}, {185, 210, 219, 155}, {185, 210, 219, 156}, {185, 216, 34, 226}, {185, 216, 34, 227}, {185, 216, 34, 228}, {185, 216, 34, 229}, {185, 216, 34, 230}, {185, 216, 34, 232}, {185, 216, 34, 236}, {185, 216, 34, 237}, {185, 216, 34, 238}}},
{Region: "Belgium", IPs: []net.IP{{77, 243, 191, 19}, {77, 243, 191, 20}, {77, 243, 191, 21}, {77, 243, 191, 22}, {77, 243, 191, 26}, {77, 243, 191, 27}, {185, 232, 21, 26}, {185, 232, 21, 27}, {185, 232, 21, 28}, {185, 232, 21, 29}}},
{Region: "CA Montreal", IPs: []net.IP{{199, 229, 249, 132}, {199, 229, 249, 134}, {199, 229, 249, 137}, {199, 229, 249, 142}, {199, 229, 249, 144}, {199, 229, 249, 146}, {199, 229, 249, 149}, {199, 229, 249, 151}, {199, 229, 249, 152}, {199, 229, 249, 155}, {199, 229, 249, 159}, {199, 229, 249, 165}, {199, 229, 249, 169}, {199, 229, 249, 172}, {199, 229, 249, 176}, {199, 229, 249, 182}, {199, 229, 249, 183}, {199, 229, 249, 184}, {199, 229, 249, 185}, {199, 229, 249, 191}}},
{Region: "CA Toronto", IPs: []net.IP{{172, 98, 67, 53}, {172, 98, 67, 71}, {172, 98, 67, 87}, {172, 98, 67, 100}}},
{Region: "CA Vancouver", IPs: []net.IP{{107, 181, 189, 71}, {107, 181, 189, 74}, {107, 181, 189, 75}, {107, 181, 189, 85}, {107, 181, 189, 86}, {172, 83, 40, 18}, {172, 83, 40, 20}, {172, 83, 40, 23}, {172, 83, 40, 24}, {172, 83, 40, 98}, {172, 83, 40, 99}, {172, 83, 40, 100}, {172, 83, 40, 101}, {172, 83, 40, 105}, {172, 83, 40, 106}, {172, 83, 40, 109}, {172, 83, 40, 110}, {172, 83, 40, 111}, {172, 83, 40, 113}, {172, 83, 40, 115}}},
{Region: "Czech Republic", IPs: []net.IP{{89, 238, 186, 226}, {89, 238, 186, 227}, {89, 238, 186, 228}, {89, 238, 186, 230}, {185, 216, 35, 66}, {185, 216, 35, 67}, {185, 216, 35, 68}, {185, 216, 35, 69}, {185, 216, 35, 70}, {185, 242, 6, 26}, {185, 242, 6, 27}, {185, 242, 6, 28}, {185, 242, 6, 29}, {185, 242, 6, 30}}},
{Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 226}, {185, 230, 127, 229}, {185, 230, 127, 230}, {185, 230, 127, 233}, {185, 230, 127, 234}, {185, 230, 127, 235}, {185, 230, 127, 237}, {185, 230, 127, 239}, {185, 230, 127, 240}, {185, 230, 127, 242}, {185, 230, 127, 243}, {193, 176, 86, 122}, {193, 176, 86, 125}, {193, 176, 86, 134}, {193, 176, 86, 146}, {193, 176, 86, 150}, {193, 176, 86, 154}, {193, 176, 86, 162}, {193, 176, 86, 166}, {193, 176, 86, 174}}},
{Region: "DE Frankfurt", IPs: []net.IP{{185, 220, 70, 133}, {185, 220, 70, 134}, {185, 220, 70, 135}, {185, 220, 70, 136}, {185, 220, 70, 137}, {185, 220, 70, 138}, {185, 220, 70, 141}, {185, 220, 70, 143}, {185, 220, 70, 144}, {185, 220, 70, 147}, {185, 220, 70, 148}, {185, 220, 70, 149}, {185, 220, 70, 150}, {185, 220, 70, 152}, {185, 220, 70, 163}, {185, 220, 70, 167}, {185, 220, 70, 170}, {185, 220, 70, 171}, {185, 220, 70, 172}, {185, 220, 70, 173}}},
{Region: "Denmark", IPs: []net.IP{{82, 102, 20, 162}, {82, 102, 20, 164}, {82, 102, 20, 165}, {82, 102, 20, 167}, {82, 102, 20, 169}, {82, 102, 20, 170}, {82, 102, 20, 171}, {82, 102, 20, 172}, {82, 102, 20, 173}, {82, 102, 20, 174}, {82, 102, 20, 175}, {82, 102, 20, 176}, {82, 102, 20, 177}, {82, 102, 20, 178}, {82, 102, 20, 179}, {82, 102, 20, 180}, {82, 102, 20, 181}, {82, 102, 20, 183}, {82, 102, 20, 184}, {82, 102, 20, 230}}},
{Region: "Finlan", IPs: []net.IP{{196, 244, 191, 2}, {196, 244, 191, 10}, {196, 244, 191, 18}, {196, 244, 191, 26}, {196, 244, 191, 34}, {196, 244, 191, 42}, {196, 244, 191, 50}, {196, 244, 191, 58}, {196, 244, 191, 66}, {196, 244, 191, 82}, {196, 244, 191, 90}, {196, 244, 191, 98}, {196, 244, 191, 106}, {196, 244, 191, 114}, {196, 244, 191, 122}, {196, 244, 191, 138}, {196, 244, 191, 146}}},
{Region: "France", IPs: []net.IP{{194, 99, 106, 147}, {194, 99, 106, 148}, {194, 99, 106, 149}, {194, 187, 249, 35}, {194, 187, 249, 36}, {194, 187, 249, 37}, {194, 187, 249, 38}, {194, 187, 249, 44}, {194, 187, 249, 46}, {194, 187, 249, 50}, {194, 187, 249, 53}, {194, 187, 249, 57}, {194, 187, 249, 58}, {194, 187, 249, 60}, {194, 187, 249, 61}, {194, 187, 249, 62}, {194, 187, 249, 178}, {194, 187, 249, 183}, {194, 187, 249, 184}, {194, 187, 249, 186}}},
{Region: "Hong Kong", IPs: []net.IP{{84, 17, 37, 23}, {84, 17, 37, 45}, {119, 81, 135, 5}, {119, 81, 135, 18}, {119, 81, 135, 26}, {119, 81, 135, 28}, {119, 81, 135, 29}, {119, 81, 135, 47}, {119, 81, 135, 51}, {119, 81, 135, 53}, {119, 81, 253, 214}, {119, 81, 253, 218}, {119, 81, 253, 229}, {119, 81, 253, 230}, {119, 81, 253, 241}, {119, 81, 253, 242}, {161, 202, 39, 202}, {161, 202, 39, 240}, {161, 202, 39, 251}, {161, 202, 44, 94}}},
{Region: "Hungary", IPs: []net.IP{{185, 128, 26, 18}, {185, 128, 26, 19}, {185, 128, 26, 20}, {185, 128, 26, 21}, {185, 128, 26, 22}, {185, 128, 26, 23}, {185, 128, 26, 24}, {185, 189, 114, 98}}},
{Region: "India", IPs: []net.IP{{150, 242, 12, 155}, {150, 242, 12, 171}, {150, 242, 12, 187}}},
{Region: "Ireland", IPs: []net.IP{{23, 92, 127, 2}, {23, 92, 127, 10}, {23, 92, 127, 18}, {23, 92, 127, 34}, {23, 92, 127, 42}, {23, 92, 127, 58}}},
{Region: "Israel", IPs: []net.IP{{31, 168, 172, 136}, {31, 168, 172, 142}, {31, 168, 172, 143}, {31, 168, 172, 145}, {31, 168, 172, 147}}},
{Region: "Italy", IPs: []net.IP{{82, 102, 21, 98}, {82, 102, 21, 210}, {82, 102, 21, 211}, {82, 102, 21, 212}, {82, 102, 21, 213}, {82, 102, 21, 214}, {82, 102, 21, 215}, {82, 102, 21, 216}, {82, 102, 21, 217}, {82, 102, 21, 218}, {82, 102, 21, 219}}},
{Region: "Japan", IPs: []net.IP{{103, 208, 220, 135}, {103, 208, 220, 136}, {103, 208, 220, 138}, {103, 208, 220, 139}, {103, 208, 220, 141}}},
{Region: "Ireland", IPs: []net.IP{{23, 92, 127, 2}, {23, 92, 127, 10}, {23, 92, 127, 18}, {23, 92, 127, 34}, {23, 92, 127, 42}, {23, 92, 127, 50}, {23, 92, 127, 58}, {23, 92, 127, 66}}},
{Region: "Israel", IPs: []net.IP{{31, 168, 172, 136}, {31, 168, 172, 142}, {31, 168, 172, 143}, {31, 168, 172, 145}, {31, 168, 172, 146}, {31, 168, 172, 147}}},
{Region: "Italy", IPs: []net.IP{{82, 102, 21, 98}, {82, 102, 21, 210}, {82, 102, 21, 212}, {82, 102, 21, 213}, {82, 102, 21, 214}, {82, 102, 21, 215}, {82, 102, 21, 216}, {82, 102, 21, 217}, {82, 102, 21, 218}, {82, 102, 21, 219}}},
{Region: "Japan", IPs: []net.IP{{103, 208, 220, 130}, {103, 208, 220, 131}, {103, 208, 220, 132}, {103, 208, 220, 133}, {103, 208, 220, 134}, {103, 208, 220, 135}, {103, 208, 220, 136}, {103, 208, 220, 137}, {103, 208, 220, 138}, {103, 208, 220, 139}, {103, 208, 220, 140}, {103, 208, 220, 141}, {103, 208, 220, 142}, {103, 208, 220, 143}}},
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 133}, {92, 223, 89, 134}, {92, 223, 89, 135}, {92, 223, 89, 136}, {92, 223, 89, 137}, {92, 223, 89, 138}, {92, 223, 89, 140}, {92, 223, 89, 142}}},
{Region: "Mexico", IPs: []net.IP{{169, 57, 0, 197}, {169, 57, 0, 200}, {169, 57, 0, 203}, {169, 57, 0, 205}, {169, 57, 0, 207}, {169, 57, 0, 210}, {169, 57, 0, 211}, {169, 57, 0, 212}, {169, 57, 0, 213}, {169, 57, 0, 217}, {169, 57, 0, 218}, {169, 57, 0, 221}, {169, 57, 0, 229}, {169, 57, 0, 230}, {169, 57, 0, 233}, {169, 57, 0, 236}, {169, 57, 0, 238}, {169, 57, 0, 243}, {169, 57, 0, 247}, {169, 57, 0, 248}}},
{Region: "Netherlands", IPs: []net.IP{{46, 166, 138, 145}, {46, 166, 138, 146}, {46, 166, 138, 155}, {46, 166, 138, 162}, {46, 166, 186, 248}, {46, 166, 188, 208}, {46, 166, 188, 210}, {46, 166, 188, 215}, {46, 166, 190, 178}, {46, 166, 190, 185}, {46, 166, 190, 186}, {46, 166, 190, 195}, {46, 166, 190, 230}, {109, 201, 138, 239}, {109, 201, 152, 5}, {109, 201, 152, 26}, {109, 201, 152, 227}, {109, 201, 154, 141}, {109, 201, 154, 142}, {185, 107, 44, 25}}},
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 3}}},
{Region: "Norway", IPs: []net.IP{{82, 102, 27, 50}, {82, 102, 27, 51}, {82, 102, 27, 52}, {82, 102, 27, 53}, {82, 102, 27, 54}, {82, 102, 27, 56}, {82, 102, 27, 74}, {82, 102, 27, 75}, {82, 102, 27, 76}, {82, 102, 27, 77}, {82, 102, 27, 78}, {82, 102, 27, 114}, {82, 102, 27, 117}, {82, 102, 27, 118}, {82, 102, 27, 125}, {82, 102, 27, 126}, {185, 206, 225, 222}, {185, 253, 97, 226}, {185, 253, 97, 227}, {185, 253, 97, 228}}},
{Region: "Netherlands", IPs: []net.IP{{46, 166, 137, 219}, {46, 166, 138, 131}, {46, 166, 186, 218}, {46, 166, 186, 238}, {46, 166, 186, 249}, {46, 166, 188, 214}, {46, 166, 188, 241}, {46, 166, 190, 215}, {85, 159, 236, 219}, {109, 201, 152, 15}, {109, 201, 152, 16}, {109, 201, 152, 25}, {109, 201, 152, 238}, {109, 201, 154, 147}}},
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 3}, {43, 250, 207, 7}}},
{Region: "Norway", IPs: []net.IP{{82, 102, 27, 50}, {82, 102, 27, 51}, {82, 102, 27, 52}, {82, 102, 27, 53}, {82, 102, 27, 55}, {82, 102, 27, 56}, {82, 102, 27, 57}, {82, 102, 27, 74}, {82, 102, 27, 77}, {82, 102, 27, 78}, {82, 102, 27, 114}, {82, 102, 27, 115}, {82, 102, 27, 116}, {82, 102, 27, 117}, {82, 102, 27, 124}, {82, 102, 27, 125}, {82, 102, 27, 126}, {185, 206, 225, 222}, {185, 253, 97, 227}, {185, 253, 97, 228}}},
{Region: "Poland", IPs: []net.IP{{185, 244, 214, 14}, {185, 244, 214, 194}, {185, 244, 214, 196}, {185, 244, 214, 197}, {185, 244, 214, 198}, {185, 244, 214, 199}, {185, 244, 214, 200}}},
{Region: "Romania", IPs: []net.IP{{86, 105, 25, 66}, {86, 105, 25, 67}, {86, 105, 25, 68}, {86, 105, 25, 69}, {86, 105, 25, 70}, {86, 105, 25, 74}, {86, 105, 25, 75}, {86, 105, 25, 76}, {86, 105, 25, 77}, {86, 105, 25, 78}, {89, 33, 8, 42}, {94, 176, 148, 34}, {94, 176, 148, 35}, {185, 45, 12, 126}, {185, 210, 218, 100}, {185, 210, 218, 103}}},
{Region: "Singapore", IPs: []net.IP{{37, 120, 208, 66}, {37, 120, 208, 67}, {37, 120, 208, 68}, {37, 120, 208, 70}, {37, 120, 208, 71}, {37, 120, 208, 72}, {37, 120, 208, 73}, {37, 120, 208, 74}, {37, 120, 208, 75}, {37, 120, 208, 76}, {37, 120, 208, 77}, {37, 120, 208, 78}, {37, 120, 208, 79}, {37, 120, 208, 80}, {37, 120, 208, 81}, {37, 120, 208, 82}, {37, 120, 208, 83}}},
{Region: "Spain", IPs: []net.IP{{37, 120, 148, 86}, {185, 230, 124, 50}, {185, 230, 124, 51}, {185, 230, 124, 52}, {185, 230, 124, 53}, {185, 230, 124, 54}, {194, 99, 104, 26}, {194, 99, 104, 27}, {194, 99, 104, 28}, {194, 99, 104, 29}, {194, 99, 104, 30}, {195, 206, 107, 250}}},
{Region: "Sweden", IPs: []net.IP{{45, 12, 220, 163}, {45, 12, 220, 168}, {45, 12, 220, 169}, {45, 12, 220, 173}, {45, 12, 220, 178}, {45, 12, 220, 180}, {45, 12, 220, 184}, {45, 12, 220, 190}, {45, 12, 220, 194}, {45, 12, 220, 197}, {45, 12, 220, 204}, {45, 12, 220, 205}, {45, 12, 220, 215}, {45, 12, 220, 218}, {45, 12, 220, 227}, {45, 12, 220, 233}, {45, 12, 220, 240}, {45, 12, 220, 254}, {45, 83, 91, 21}, {45, 83, 91, 36}}},
{Region: "Switzerland", IPs: []net.IP{{82, 102, 24, 171}, {82, 102, 24, 250}, {82, 102, 24, 252}, {91, 132, 136, 52}, {91, 132, 136, 54}, {91, 132, 136, 210}, {185, 156, 175, 82}, {185, 156, 175, 88}, {185, 156, 175, 90}, {185, 156, 175, 94}, {185, 212, 170, 179}, {185, 212, 170, 182}, {185, 212, 170, 187}, {185, 230, 125, 34}, {185, 230, 125, 36}, {185, 230, 125, 45}, {185, 230, 125, 48}, {185, 230, 125, 52}, {185, 230, 125, 90}, {212, 102, 36, 1}}},
{Region: "Romania", IPs: []net.IP{{86, 105, 25, 66}, {86, 105, 25, 67}, {86, 105, 25, 68}, {86, 105, 25, 69}, {86, 105, 25, 70}, {86, 105, 25, 74}, {86, 105, 25, 75}, {86, 105, 25, 76}, {86, 105, 25, 77}, {86, 105, 25, 78}, {94, 176, 148, 34}, {185, 45, 12, 126}, {185, 210, 218, 98}, {185, 210, 218, 99}, {185, 210, 218, 100}, {185, 210, 218, 101}, {185, 210, 218, 104}, {185, 210, 218, 105}, {185, 210, 218, 108}}},
{Region: "Singapore", IPs: []net.IP{{37, 120, 208, 66}, {37, 120, 208, 67}, {37, 120, 208, 68}, {37, 120, 208, 70}, {37, 120, 208, 72}, {37, 120, 208, 73}, {37, 120, 208, 74}, {37, 120, 208, 75}, {37, 120, 208, 76}, {37, 120, 208, 77}, {37, 120, 208, 78}, {37, 120, 208, 79}, {37, 120, 208, 81}, {37, 120, 208, 82}, {37, 120, 208, 83}}},
{Region: "Spain", IPs: []net.IP{{185, 230, 124, 53}, {194, 99, 104, 26}, {194, 99, 104, 30}}},
{Region: "Sweden", IPs: []net.IP{{45, 12, 220, 182}, {45, 12, 220, 183}, {45, 12, 220, 194}, {45, 12, 220, 195}, {45, 12, 220, 203}, {45, 12, 220, 206}, {45, 12, 220, 209}, {45, 12, 220, 217}, {45, 12, 220, 228}, {45, 12, 220, 234}, {45, 12, 220, 240}, {45, 12, 220, 243}, {45, 12, 220, 245}, {45, 12, 220, 248}, {45, 12, 220, 250}, {45, 12, 220, 253}}},
{Region: "Switzerland", IPs: []net.IP{{82, 102, 24, 174}, {82, 102, 24, 250}, {91, 132, 136, 42}, {91, 132, 136, 43}, {91, 132, 136, 52}, {185, 156, 175, 83}, {185, 156, 175, 84}, {185, 156, 175, 85}, {185, 156, 175, 91}, {185, 156, 175, 92}, {185, 156, 175, 93}, {185, 230, 125, 36}, {185, 230, 125, 46}, {185, 230, 125, 48}, {185, 230, 125, 50}, {185, 230, 125, 52}, {185, 230, 125, 86}, {185, 230, 125, 90}, {195, 206, 105, 210}, {212, 102, 36, 1}}},
{Region: "UAE", IPs: []net.IP{{45, 9, 250, 42}, {45, 9, 250, 46}, {45, 9, 250, 62}}},
{Region: "UK London", IPs: []net.IP{{89, 238, 150, 9}, {89, 238, 150, 11}, {89, 238, 150, 15}, {89, 238, 150, 20}, {89, 238, 154, 18}, {89, 238, 154, 20}, {89, 238, 154, 23}, {89, 238, 154, 120}, {89, 238, 154, 121}, {89, 238, 154, 162}, {89, 238, 154, 167}, {89, 238, 154, 174}, {89, 238, 154, 179}, {89, 238, 154, 236}, {89, 238, 154, 243}, {89, 238, 154, 245}, {89, 238, 154, 249}, {89, 238, 154, 250}, {89, 238, 154, 251}, {89, 238, 154, 254}}},
{Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 38}, {89, 238, 137, 39}, {89, 238, 137, 40}, {89, 238, 137, 41}, {89, 238, 139, 4}, {89, 238, 139, 5}, {89, 238, 139, 7}, {89, 238, 139, 8}, {89, 238, 139, 9}, {89, 238, 139, 10}, {89, 238, 139, 11}, {89, 238, 139, 13}, {89, 238, 139, 53}, {89, 238, 139, 55}, {89, 238, 139, 56}, {89, 238, 139, 57}, {89, 238, 139, 58}, {89, 249, 67, 220}}},
{Region: "UK Southampton", IPs: []net.IP{{31, 24, 226, 141}, {31, 24, 226, 145}, {31, 24, 226, 146}, {31, 24, 226, 205}, {31, 24, 226, 207}, {31, 24, 226, 208}, {31, 24, 226, 209}, {31, 24, 226, 217}, {31, 24, 226, 220}, {31, 24, 226, 223}, {31, 24, 226, 225}, {31, 24, 226, 226}, {31, 24, 226, 228}, {31, 24, 226, 232}, {31, 24, 226, 233}, {31, 24, 226, 234}, {31, 24, 226, 237}, {31, 24, 226, 244}, {31, 24, 226, 245}, {31, 24, 231, 197}}},
{Region: "US Atlanta", IPs: []net.IP{{66, 115, 168, 2}, {66, 115, 168, 4}, {66, 115, 168, 7}, {66, 115, 168, 10}, {66, 115, 168, 13}, {66, 115, 168, 17}, {66, 115, 168, 19}, {66, 115, 168, 21}, {66, 115, 168, 23}, {66, 115, 168, 26}, {66, 115, 168, 27}, {66, 115, 168, 28}, {66, 115, 169, 198}, {66, 115, 169, 202}, {66, 115, 169, 204}, {66, 115, 169, 226}, {66, 115, 169, 229}, {66, 115, 169, 231}, {66, 115, 169, 242}, {66, 115, 169, 244}}},
{Region: "US California", IPs: []net.IP{{91, 207, 175, 46}, {91, 207, 175, 62}, {91, 207, 175, 100}, {91, 207, 175, 102}, {91, 207, 175, 115}, {91, 207, 175, 121}, {91, 207, 175, 170}, {91, 207, 175, 181}, {91, 207, 175, 204}, {91, 207, 175, 206}, {91, 207, 175, 213}, {91, 207, 175, 226}, {91, 207, 175, 242}, {91, 207, 175, 250}, {91, 207, 175, 252}, {185, 245, 87, 181}, {185, 245, 87, 197}, {185, 245, 87, 215}, {212, 103, 49, 164}, {212, 103, 49, 168}}},
{Region: "US Chicago", IPs: []net.IP{{104, 200, 153, 97}, {199, 116, 115, 130}, {199, 116, 115, 131}, {199, 116, 115, 132}, {199, 116, 115, 133}, {199, 116, 115, 134}, {199, 116, 115, 135}, {199, 116, 115, 136}, {199, 116, 115, 137}, {199, 116, 115, 138}, {199, 116, 115, 139}, {199, 116, 115, 140}, {199, 116, 115, 141}, {199, 116, 115, 142}, {199, 116, 115, 143}, {199, 116, 115, 144}, {199, 116, 115, 145}, {199, 116, 115, 146}, {199, 116, 115, 147}, {199, 116, 115, 148}}},
{Region: "US Denver", IPs: []net.IP{{174, 128, 225, 106}, {174, 128, 225, 186}, {174, 128, 226, 18}, {174, 128, 227, 226}, {174, 128, 236, 98}, {174, 128, 236, 106}, {174, 128, 242, 234}, {174, 128, 244, 74}, {174, 128, 245, 106}, {198, 148, 82, 82}, {198, 148, 90, 58}, {199, 115, 98, 146}, {199, 115, 98, 154}, {199, 115, 99, 218}}},
{Region: "US East", IPs: []net.IP{{193, 37, 253, 38}, {193, 37, 253, 115}, {194, 59, 251, 6}, {194, 59, 251, 13}, {194, 59, 251, 14}, {194, 59, 251, 17}, {194, 59, 251, 28}, {194, 59, 251, 29}, {194, 59, 251, 58}, {194, 59, 251, 66}, {194, 59, 251, 74}, {194, 59, 251, 81}, {194, 59, 251, 104}, {194, 59, 251, 111}, {194, 59, 251, 112}, {194, 59, 251, 149}, {194, 59, 251, 166}, {194, 59, 251, 216}, {194, 59, 251, 227}, {194, 59, 251, 240}}},
{Region: "US Florida", IPs: []net.IP{{193, 37, 252, 3}, {193, 37, 252, 12}, {193, 37, 252, 41}, {193, 37, 252, 45}, {193, 37, 252, 46}, {193, 37, 252, 52}, {193, 37, 252, 54}, {193, 37, 252, 55}, {193, 37, 252, 56}, {193, 37, 252, 58}, {193, 37, 252, 59}, {193, 37, 252, 75}, {193, 37, 252, 76}, {193, 37, 252, 102}, {193, 37, 252, 108}, {193, 37, 252, 115}, {193, 37, 252, 116}, {193, 37, 252, 118}, {193, 37, 252, 170}, {193, 37, 252, 174}}},
{Region: "US Houston", IPs: []net.IP{{74, 81, 88, 18}, {74, 81, 88, 26}, {74, 81, 88, 66}, {74, 81, 88, 82}, {74, 81, 88, 114}, {74, 81, 88, 130}, {74, 81, 88, 162}, {205, 251, 148, 34}, {205, 251, 148, 66}, {205, 251, 148, 82}, {205, 251, 148, 98}, {205, 251, 148, 130}, {205, 251, 148, 178}, {205, 251, 150, 146}, {205, 251, 150, 154}, {205, 251, 150, 162}, {205, 251, 150, 170}, {205, 251, 150, 194}, {205, 251, 150, 218}, {205, 251, 150, 234}}},
{Region: "US Las Vegas", IPs: []net.IP{{162, 251, 236, 2}, {162, 251, 236, 3}, {162, 251, 236, 4}, {162, 251, 236, 5}, {162, 251, 236, 7}, {162, 251, 236, 8}, {162, 251, 236, 9}, {199, 127, 56, 82}, {199, 127, 56, 83}, {199, 127, 56, 84}, {199, 127, 56, 86}, {199, 127, 56, 88}, {199, 127, 56, 89}, {199, 127, 56, 91}, {199, 127, 56, 114}, {199, 127, 56, 115}, {199, 127, 56, 118}, {199, 127, 56, 119}, {199, 127, 56, 120}, {199, 127, 56, 121}}},
{Region: "US New York City", IPs: []net.IP{{107, 182, 231, 24}, {107, 182, 231, 34}, {173, 244, 223, 122}, {209, 95, 50, 13}, {209, 95, 50, 17}, {209, 95, 50, 18}, {209, 95, 50, 27}, {209, 95, 50, 49}, {209, 95, 50, 50}, {209, 95, 50, 56}, {209, 95, 50, 84}, {209, 95, 50, 87}, {209, 95, 50, 89}, {209, 95, 50, 93}, {209, 95, 50, 134}, {209, 95, 50, 139}, {209, 95, 50, 147}, {209, 95, 50, 148}, {209, 95, 50, 162}, {209, 95, 50, 163}}},
{Region: "US Seattle", IPs: []net.IP{{104, 200, 154, 12}, {104, 200, 154, 38}, {104, 200, 154, 39}, {104, 200, 154, 50}, {104, 200, 154, 65}, {104, 200, 154, 66}, {104, 200, 154, 68}, {104, 200, 154, 70}, {104, 200, 154, 72}, {104, 200, 154, 73}, {104, 200, 154, 74}, {104, 200, 154, 75}, {104, 200, 154, 78}, {104, 200, 154, 79}, {104, 200, 154, 84}, {104, 200, 154, 86}, {104, 200, 154, 88}, {104, 200, 154, 90}, {104, 200, 154, 91}, {104, 200, 154, 98}}},
{Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 131}, {199, 116, 118, 132}, {199, 116, 118, 133}, {199, 116, 118, 137}, {199, 116, 118, 140}, {199, 116, 118, 155}, {199, 116, 118, 156}, {199, 116, 118, 170}, {199, 116, 118, 173}, {199, 116, 118, 178}, {199, 116, 118, 184}, {199, 116, 118, 201}, {199, 116, 118, 202}, {199, 116, 118, 209}, {199, 116, 118, 210}, {199, 116, 118, 228}, {199, 116, 118, 237}, {199, 116, 118, 238}, {199, 116, 118, 244}, {199, 116, 118, 250}}},
{Region: "US Texas", IPs: []net.IP{{162, 216, 46, 5}, {162, 216, 46, 10}, {162, 216, 46, 14}, {162, 216, 46, 18}, {162, 216, 46, 36}, {162, 216, 46, 38}, {162, 216, 46, 39}, {162, 216, 46, 42}, {162, 216, 46, 58}, {162, 216, 46, 71}, {162, 216, 46, 82}, {162, 216, 46, 85}, {162, 216, 46, 104}, {162, 216, 46, 105}, {162, 216, 46, 132}, {162, 216, 46, 138}, {162, 216, 46, 150}, {162, 216, 46, 151}, {162, 216, 46, 153}, {162, 216, 46, 170}}},
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 24}, {70, 32, 0, 30}, {70, 32, 0, 54}, {70, 32, 0, 64}, {70, 32, 0, 68}, {70, 32, 0, 77}, {70, 32, 0, 111}, {70, 32, 0, 120}, {70, 32, 0, 121}, {70, 32, 0, 137}, {70, 32, 0, 153}, {70, 32, 0, 155}, {70, 32, 0, 165}, {70, 32, 0, 166}, {70, 32, 0, 167}, {70, 32, 0, 170}, {70, 32, 0, 172}, {70, 32, 0, 177}, {70, 32, 0, 178}, {70, 32, 0, 179}}},
{Region: "US West", IPs: []net.IP{{104, 200, 151, 3}, {104, 200, 151, 11}, {104, 200, 151, 14}, {104, 200, 151, 15}, {104, 200, 151, 21}, {104, 200, 151, 28}, {104, 200, 151, 29}, {104, 200, 151, 34}, {104, 200, 151, 36}, {104, 200, 151, 42}, {104, 200, 151, 44}, {104, 200, 151, 47}, {104, 200, 151, 50}, {104, 200, 151, 51}, {104, 200, 151, 54}, {104, 200, 151, 60}, {104, 200, 151, 72}, {104, 200, 151, 75}, {104, 200, 151, 77}, {104, 200, 151, 78}}},
{Region: "UK London", IPs: []net.IP{{89, 238, 150, 7}, {89, 238, 150, 13}, {89, 238, 150, 18}, {89, 238, 154, 18}, {89, 238, 154, 19}, {89, 238, 154, 20}, {89, 238, 154, 24}, {89, 238, 154, 115}, {89, 238, 154, 118}, {89, 238, 154, 120}, {89, 238, 154, 165}, {89, 238, 154, 166}, {89, 238, 154, 171}, {89, 238, 154, 233}, {89, 238, 154, 238}, {89, 238, 154, 243}, {89, 238, 154, 245}, {89, 238, 154, 250}, {89, 238, 154, 251}, {89, 238, 154, 254}}},
{Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 39}, {89, 238, 137, 40}, {89, 238, 137, 42}, {89, 238, 139, 4}, {89, 238, 139, 5}, {89, 238, 139, 6}, {89, 238, 139, 8}, {89, 238, 139, 9}, {89, 238, 139, 10}, {89, 238, 139, 11}, {89, 238, 139, 12}, {89, 238, 139, 13}, {89, 238, 139, 52}, {89, 238, 139, 54}, {89, 238, 139, 55}, {89, 238, 139, 57}, {89, 238, 139, 58}, {89, 249, 67, 220}}},
{Region: "UK Southampton", IPs: []net.IP{{31, 24, 226, 134}, {31, 24, 226, 188}, {31, 24, 226, 189}, {31, 24, 226, 202}, {31, 24, 226, 204}, {31, 24, 226, 205}, {31, 24, 226, 209}, {31, 24, 226, 220}, {31, 24, 226, 222}, {31, 24, 226, 226}, {31, 24, 226, 227}, {31, 24, 226, 228}, {31, 24, 226, 230}, {31, 24, 226, 234}, {31, 24, 226, 237}, {31, 24, 226, 240}, {31, 24, 226, 241}, {31, 24, 226, 244}, {31, 24, 226, 245}, {31, 24, 226, 246}}},
{Region: "US Atlanta", IPs: []net.IP{{156, 146, 46, 1}, {156, 146, 46, 198}}},
{Region: "US California", IPs: []net.IP{{91, 207, 175, 38}, {91, 207, 175, 42}, {91, 207, 175, 55}, {91, 207, 175, 60}, {91, 207, 175, 71}, {91, 207, 175, 109}, {91, 207, 175, 110}, {91, 207, 175, 118}, {91, 207, 175, 125}, {91, 207, 175, 163}, {91, 207, 175, 172}, {91, 207, 175, 182}, {91, 207, 175, 204}, {91, 207, 175, 205}, {91, 207, 175, 234}, {185, 245, 87, 170}, {185, 245, 87, 181}, {185, 245, 87, 195}, {185, 245, 87, 199}, {185, 245, 87, 220}}},
{Region: "US Chicago", IPs: []net.IP{{156, 146, 50, 65}, {156, 146, 50, 134}, {156, 146, 50, 198}, {156, 146, 51, 11}, {212, 102, 58, 113}, {212, 102, 59, 54}}},
{Region: "US Denver", IPs: []net.IP{{174, 128, 225, 186}, {174, 128, 226, 18}, {174, 128, 236, 98}, {174, 128, 242, 234}, {174, 128, 243, 98}, {174, 128, 243, 106}, {174, 128, 244, 66}, {174, 128, 244, 74}, {174, 128, 245, 106}, {174, 128, 245, 122}, {174, 128, 250, 18}, {174, 128, 250, 26}, {198, 148, 90, 58}, {199, 115, 97, 202}, {199, 115, 98, 154}, {199, 115, 99, 82}, {199, 115, 99, 218}, {199, 115, 101, 178}, {199, 115, 101, 186}, {199, 115, 103, 10}}},
{Region: "US East", IPs: []net.IP{{193, 37, 253, 38}, {193, 37, 253, 86}, {194, 59, 251, 7}, {194, 59, 251, 13}, {194, 59, 251, 22}, {194, 59, 251, 48}, {194, 59, 251, 59}, {194, 59, 251, 103}, {194, 59, 251, 131}, {194, 59, 251, 133}, {194, 59, 251, 136}, {194, 59, 251, 137}, {194, 59, 251, 152}, {194, 59, 251, 169}, {194, 59, 251, 174}, {194, 59, 251, 190}, {194, 59, 251, 213}, {194, 59, 251, 219}, {194, 59, 251, 233}, {194, 59, 251, 239}}},
{Region: "US Florida", IPs: []net.IP{{193, 37, 252, 7}, {193, 37, 252, 8}, {193, 37, 252, 9}, {193, 37, 252, 17}, {193, 37, 252, 19}, {193, 37, 252, 34}, {193, 37, 252, 39}, {193, 37, 252, 45}, {193, 37, 252, 58}, {193, 37, 252, 59}, {193, 37, 252, 67}, {193, 37, 252, 68}, {193, 37, 252, 76}, {193, 37, 252, 82}, {193, 37, 252, 106}, {193, 37, 252, 115}, {193, 37, 252, 116}, {193, 37, 252, 117}, {193, 37, 252, 125}, {193, 37, 252, 126}}},
{Region: "US Houston", IPs: []net.IP{{74, 81, 88, 18}, {74, 81, 88, 26}, {74, 81, 88, 42}, {74, 81, 88, 58}, {74, 81, 88, 66}, {74, 81, 88, 82}, {74, 81, 88, 90}, {74, 81, 88, 114}, {74, 81, 88, 162}, {205, 251, 148, 50}, {205, 251, 148, 58}, {205, 251, 148, 98}, {205, 251, 148, 138}, {205, 251, 148, 178}, {205, 251, 150, 154}, {205, 251, 150, 162}, {205, 251, 150, 194}, {205, 251, 150, 218}, {205, 251, 150, 226}, {205, 251, 151, 26}}},
{Region: "US Las Vegas", IPs: []net.IP{{162, 251, 236, 2}, {162, 251, 236, 3}, {162, 251, 236, 4}, {162, 251, 236, 7}, {162, 251, 236, 8}, {162, 251, 236, 9}, {199, 127, 56, 83}, {199, 127, 56, 84}, {199, 127, 56, 86}, {199, 127, 56, 87}, {199, 127, 56, 88}, {199, 127, 56, 89}, {199, 127, 56, 91}, {199, 127, 56, 114}, {199, 127, 56, 115}, {199, 127, 56, 116}, {199, 127, 56, 117}, {199, 127, 56, 118}, {199, 127, 56, 119}, {199, 127, 56, 120}}},
{Region: "US New York City", IPs: []net.IP{{107, 182, 230, 194}, {107, 182, 231, 24}, {107, 182, 231, 30}, {107, 182, 231, 34}, {107, 182, 231, 37}, {107, 182, 231, 38}, {107, 182, 231, 51}, {209, 95, 50, 12}, {209, 95, 50, 27}, {209, 95, 50, 50}, {209, 95, 50, 65}, {209, 95, 50, 66}, {209, 95, 50, 90}, {209, 95, 50, 93}, {209, 95, 50, 103}, {209, 95, 50, 104}, {209, 95, 50, 133}, {209, 95, 50, 144}, {209, 95, 50, 146}, {209, 95, 50, 162}}},
{Region: "US Seattle", IPs: []net.IP{{104, 200, 154, 11}, {104, 200, 154, 21}, {104, 200, 154, 22}, {104, 200, 154, 44}, {104, 200, 154, 47}, {104, 200, 154, 56}, {104, 200, 154, 59}, {104, 200, 154, 62}, {104, 200, 154, 66}, {104, 200, 154, 67}, {104, 200, 154, 70}, {104, 200, 154, 81}, {104, 200, 154, 84}, {104, 200, 154, 87}, {104, 200, 154, 90}, {104, 200, 154, 91}, {104, 200, 154, 96}, {104, 200, 154, 97}, {104, 200, 154, 98}, {104, 200, 154, 99}}},
{Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 130}, {199, 116, 118, 133}, {199, 116, 118, 137}, {199, 116, 118, 145}, {199, 116, 118, 159}, {199, 116, 118, 160}, {199, 116, 118, 178}, {199, 116, 118, 181}, {199, 116, 118, 182}, {199, 116, 118, 202}, {199, 116, 118, 205}, {199, 116, 118, 209}, {199, 116, 118, 211}, {199, 116, 118, 213}, {199, 116, 118, 217}, {199, 116, 118, 218}, {199, 116, 118, 236}, {199, 116, 118, 238}, {199, 116, 118, 244}, {199, 116, 118, 252}}},
{Region: "US Texas", IPs: []net.IP{{162, 216, 46, 33}, {162, 216, 46, 40}, {162, 216, 46, 44}, {162, 216, 46, 57}, {162, 216, 46, 58}, {162, 216, 46, 70}, {162, 216, 46, 81}, {162, 216, 46, 96}, {162, 216, 46, 101}, {162, 216, 46, 112}, {162, 216, 46, 119}, {162, 216, 46, 123}, {162, 216, 46, 129}, {162, 216, 46, 132}, {162, 216, 46, 133}, {162, 216, 46, 141}, {162, 216, 46, 148}, {162, 216, 46, 154}, {162, 216, 46, 156}, {162, 216, 46, 168}}},
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 24}, {70, 32, 0, 46}, {70, 32, 0, 53}, {70, 32, 0, 55}, {70, 32, 0, 61}, {70, 32, 0, 74}, {70, 32, 0, 97}, {70, 32, 0, 103}, {70, 32, 0, 104}, {70, 32, 0, 133}, {70, 32, 0, 135}, {70, 32, 0, 140}, {70, 32, 0, 153}, {70, 32, 0, 155}, {70, 32, 0, 166}, {70, 32, 0, 167}, {70, 32, 0, 168}, {70, 32, 0, 172}, {70, 32, 0, 177}, {70, 32, 0, 179}}},
{Region: "US West", IPs: []net.IP{{104, 200, 151, 4}, {104, 200, 151, 10}, {104, 200, 151, 11}, {104, 200, 151, 14}, {104, 200, 151, 15}, {104, 200, 151, 17}, {104, 200, 151, 18}, {104, 200, 151, 21}, {104, 200, 151, 26}, {104, 200, 151, 30}, {104, 200, 151, 34}, {104, 200, 151, 42}, {104, 200, 151, 48}, {104, 200, 151, 54}, {104, 200, 151, 55}, {104, 200, 151, 73}, {104, 200, 151, 78}, {104, 200, 151, 82}, {104, 200, 151, 84}, {104, 200, 151, 86}}},
}
}

View File

@@ -0,0 +1,198 @@
package constants
import (
"net"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
const (
PurevpnCertificateAuthority = "MIIE6DCCA9CgAwIBAgIJAMjXFoeo5uSlMA0GCSqGSIb3DQEBCwUAMIGoMQswCQYDVQQGEwJISzEQMA4GA1UECBMHQ2VudHJhbDELMAkGA1UEBxMCSEsxGDAWBgNVBAoTD1NlY3VyZS1TZXJ2ZXJDQTELMAkGA1UECxMCSVQxGDAWBgNVBAMTD1NlY3VyZS1TZXJ2ZXJDQTEYMBYGA1UEKRMPU2VjdXJlLVNlcnZlckNBMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWluMB4XDTE2MDExNTE1MzQwOVoXDTI2MDExMjE1MzQwOVowgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDluufhyLlyvXzPUL16kAWAdivl1roQv3QHbuRshyKacf/1Er1JqEbtW3Mx9Fvr/u27qU2W8lQI6DaJhU2BfijPe/KHkib55mvHzIVvoexxya26nk79F2c+d9PnuuMdThWQO3El5a/i2AASnM7T7piIBT2WRZW2i8RbfJaTT7G7LP7OpMKIV1qyBg/cWoO7cIWQW4jmzqrNryIkF0AzStLN1DxvnQZwgXBGv0CwuAkfQuNSLu0PQgPp0PhdukNZFllv5D29IhPr0Z+kwPtrAgPQo+lHlOBHBMUpDT4XChTPeAvMaUSBsqmonAE8UUHEabWrqYN/kWNHCNkYXMkiVmK1AgMBAAGjggERMIIBDTAdBgNVHQ4EFgQU456ijsFrYnzHBShLAPpOUqQ+Z2cwgd0GA1UdIwSB1TCB0oAU456ijsFrYnzHBShLAPpOUqQ+Z2ehga6kgaswgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQDI1xaHqObkpTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCvga2HMwOtUxWH/inL2qk24KX2pxLg939JNhqoyNrUpbDHag5xPQYXUmUpKrNJZ0z+o/ZnNUPHydTSXE7Z7E45J0GDN5E7g4pakndKnDLSjp03NgGsCGW+cXnz6UBPM5FStFvGdDeModeSUyoS9fjk+mYROvmiy5EiVDP91sKGcPLR7Ym0M7zl2aaqV7bb98HmMoBOxpeZQinof67nKrCsgz/xjktWFgcmPl4/PQSsmqQD0fTtWxGuRX+FzwvF2OCMCAJgp1RqJNlk2g50/kBIoJVPPCfjDFeDU5zGaWGSQ9+z1L6/z7VXdjUiHL0ouOcHwbiS4ZjTr9nMn6WdAHU2"
PurevpnCertificate = "MIIEnzCCA4egAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBqDELMAkGA1UEBhMCSEsxEDAOBgNVBAgTB0NlbnRyYWwxCzAJBgNVBAcTAkhLMRgwFgYDVQQKEw9TZWN1cmUtU2VydmVyQ0ExCzAJBgNVBAsTAklUMRgwFgYDVQQDEw9TZWN1cmUtU2VydmVyQ0ExGDAWBgNVBCkTD1NlY3VyZS1TZXJ2ZXJDQTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjAeFw0xNjAxMTUxNjE1MzhaFw0yNjAxMTIxNjE1MzhaMIGdMQswCQYDVQQGEwJISzEQMA4GA1UECBMHQ2VudHJhbDELMAkGA1UEBxMCSEsxFjAUBgNVBAoTDVNlY3VyZS1DbGllbnQxCzAJBgNVBAsTAklUMRYwFAYDVQQDEw1TZWN1cmUtQ2xpZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxsnyn4v6xxDPnuDaYS0b9M1N8nxgg7OBPBlK+FWRxdTQ8yxt5U5CZGm7riVp7fya2J2iPZIgmHQEv/KbxztsHAVlYSfYYlalrnhEL3bDP2tY+N43AwB1k5BrPq2s1pPLT2XG951drDKG4PUuFHUP1sHzW5oQlfVCmxgIMAP8OYkCAwEAAaOCAV8wggFbMAkGA1UdEwQCMAAwLQYJYIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU9MwUnUDbQKKZKjoeieD2OD5NlAEwgd0GA1UdIwSB1TCB0oAU456ijsFrYnzHBShLAPpOUqQ+Z2ehga6kgaswgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQDI1xaHqObkpTATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBAFyFo2VUX/UFixsdPdK9/Yt6mkCWc+XS1xbapGXXb9U1d+h1iBCIV9odUHgNCXWpz1hR5Uu/OCzaZ0asLE4IFMZlQmJs8sMT0c1tfPPGW45vxbL0lhqnQ8PNcBH7huNK7VFjUh4szXRKmaQPaM4S91R3L4CaNfVeHfAg7mN2m9Zn5Gto1Q1/CFMGKu2hxwGEw5p+X1czBWEvg/O09ckx/ggkkI1NcZsNiYQ+6Pz8DdGGX3+05YwLZu94+O6iIMrzxl/il0eK83g3YPbsOrASARvw6w/8sOnJCK5eOacl21oww875KisnYdWjHB1FiI+VzQ1/gyoDsL5kPTJVuu2CoG8="
PurevpnKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMbJ8p+L+scQz57g2mEtG/TNTfJ8YIOzgTwZSvhVkcXU0PMsbeVOQmRpu64lae38mtidoj2SIJh0BL/ym8c7bBwFZWEn2GJWpa54RC92wz9rWPjeNwMAdZOQaz6trNaTy09lxvedXawyhuD1LhR1D9bB81uaEJX1QpsYCDAD/DmJAgMBAAECgYEAvTHbDupE5U0krUvHzBEIuHblptGlcfNYHoDcD3oxYR3pOGeiuElBexv+mgHVzcFLBrsQfJUlHLPfCWi3xmjRvDQcr7N7U1u7NIzazy/PpRBaKolMRiM1KMYi2DG0i4ZONwFT8bvNHOIrZzCLY54KDrqOn55OzC70WYjWh4t5evkCQQDkkzZUAeskBC9+JP/zLps8jhwfoLBWGw/zbC9ePDmX0N8MTZdcUpg6KUTf1wbkLUyVtIRjS2ao6qu1jWG6K0x3AkEA3qPWyaWQWCynhNDqu2U1cPb2kh5AJip+gqxO3emikAdajsSxeoyEC2AfyBITbeB1tvCUZH17J4i/0+OFTEQp/wJAb/zEOGJ8PzghwK8GC7JA8mk51DEZVAaMSRovFv9wxDXcoh191AjPdmdzzCuAv9iF1i8MUc3GbWoUWK39PIYsPwJAWh63sqfx5b8tj/WBDpnJKBDPfhYAoXJSA1L8GZeY1fQkE+ZKcPCwAmrGcpXeh3t0Krj3WDXyw+32uC5Apr5wwQJAPZwOOReaC4YNfBPZN9BdHvVjOYGGUffpI+X+hWpLRnQFJteAi+eqwyk0Oi0SkJB+a7jcerK2d7q7xhec5WHlng=="
PurevpnOpenvpnStaticKeyV1 = "e30af995f56d07426d9ba1f824730521d4283db4b4d0cdda9c6e8759a3799dcb7939b6a5989160c9660de0f6125cbb1f585b41c074b2fe88ecfcf17eab9a33be1352379cdf74952b588fb161a93e13df9135b2b29038231e02d657a6225705e6868ccb0c384ed11614690a1894bfbeb274cebf1fe9c2329bdd5c8a40fe8820624d2ea7540cd79ab76892db51fc371a3ac5fc9573afecb3fffe3281e61d72e91579d9b03d8cbf7909b3aebf4d90850321ee6b7d0a7846d15c27d8290e031e951e19438a4654663cad975e138f5bc5af89c737ad822f27e19057731f41e1e254cc9c95b7175c622422cde9f1f2cfd3510add94498b4d7133d3729dd214a16b27fb"
)
func PurevpnRegionChoices() (choices []string) {
servers := PurevpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return choices
}
func PurevpnCountryChoices() (choices []string) {
servers := PurevpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return choices
}
func PurevpnCityChoices() (choices []string) {
servers := PurevpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return choices
}
func PurevpnServers() []models.PurevpnServer {
return []models.PurevpnServer{
{Region: "Africa", Country: "Algeria", City: "Algiers", IPs: []net.IP{{172, 94, 64, 2}}},
{Region: "Africa", Country: "Angola", City: "Benguela", IPs: []net.IP{{45, 115, 26, 2}}},
{Region: "Africa", Country: "Cape Verde", City: "Praia", IPs: []net.IP{{45, 74, 25, 2}}},
{Region: "Africa", Country: "Egypt", City: "Cairo", IPs: []net.IP{{192, 198, 120, 122}}},
{Region: "Africa", Country: "Ethiopia", City: "Addis Ababa", IPs: []net.IP{{104, 250, 178, 4}}},
{Region: "Africa", Country: "Ghana", City: "Accra", IPs: []net.IP{{196, 251, 67, 4}}},
{Region: "Africa", Country: "Kenya", City: "Mombasa", IPs: []net.IP{{154, 127, 57, 151}}},
{Region: "Africa", Country: "Madagascar", City: "Antananarivo", IPs: []net.IP{{206, 123, 156, 131}}},
{Region: "Africa", Country: "Mauritania", City: "Nouakchott", IPs: []net.IP{{206, 123, 158, 63}}},
{Region: "Africa", Country: "Mauritius", City: "Port Louis", IPs: []net.IP{{104, 250, 181, 4}}},
{Region: "Africa", Country: "Morocco", City: "Rabat", IPs: []net.IP{{104, 243, 250, 126}}},
{Region: "Africa", Country: "Niger", City: "Niamey", IPs: []net.IP{{206, 123, 157, 131}}},
{Region: "Africa", Country: "Nigeria", City: "Suleja", IPs: []net.IP{{102, 165, 25, 38}}},
{Region: "Africa", Country: "Senegal", City: "Dakar", IPs: []net.IP{{206, 123, 158, 131}}},
{Region: "Africa", Country: "Seychelles", City: "Victoria", IPs: []net.IP{{172, 111, 128, 126}}},
{Region: "Africa", Country: "South Africa", City: "Johannesburg", IPs: []net.IP{{102, 165, 3, 34}}},
{Region: "Africa", Country: "Tanzania", City: "Dar Es Salaam", IPs: []net.IP{{102, 135, 0, 2}}},
{Region: "Africa", Country: "Tunisia", City: "Tunis", IPs: []net.IP{{206, 123, 159, 4}}},
{Region: "Asia", Country: "Afghanistan", City: "Kabul", IPs: []net.IP{{172, 111, 208, 2}}},
{Region: "Asia", Country: "Armenia", City: "Singapore", IPs: []net.IP{{37, 120, 208, 147}}},
{Region: "Asia", Country: "Azerbaijan", City: "Baku", IPs: []net.IP{{104, 250, 177, 4}}},
{Region: "Asia", Country: "Bangladesh", City: "Dhaka", IPs: []net.IP{{206, 123, 154, 190}}},
{Region: "Asia", Country: "Brunei Darussalam", City: "Bandar Seri Begawan", IPs: []net.IP{{119, 81, 75, 84}, {119, 81, 242, 8}}},
{Region: "Asia", Country: "Cambodia", City: "Phnom Penh", IPs: []net.IP{{104, 250, 176, 122}}},
{Region: "Asia", Country: "Hong Kong (SAR)", City: "Hong Kong", IPs: []net.IP{{46, 243, 250, 4}}},
{Region: "Asia", Country: "India", City: "Chennai", IPs: []net.IP{{129, 227, 107, 242}}},
{Region: "Asia", Country: "Indonesia", City: "Jakarta", IPs: []net.IP{{103, 55, 9, 2}}},
{Region: "Asia", Country: "Japan", City: "Tokyo", IPs: []net.IP{{172, 94, 56, 2}}},
{Region: "Asia", Country: "Kazakhstan", City: "Almaty", IPs: []net.IP{{206, 123, 152, 4}}},
{Region: "Asia", Country: "Korea, South", City: "Seoul", IPs: []net.IP{{45, 115, 25, 2}}},
{Region: "Asia", Country: "Kyrgyzstan", City: "Bishkek", IPs: []net.IP{{206, 123, 151, 131}}},
{Region: "Asia", Country: "Laos", City: "Vientiane", IPs: []net.IP{{206, 123, 153, 4}}},
{Region: "Asia", Country: "Macao", City: "Beyrouth", IPs: []net.IP{{104, 243, 240, 121}}},
{Region: "Asia", Country: "Malaysia", City: "Johor Baharu", IPs: []net.IP{{43, 226, 230, 4}}},
{Region: "Asia", Country: "Malaysia", City: "Kuala Lumpur", IPs: []net.IP{{104, 250, 160, 4}}},
{Region: "Asia", Country: "Mongolia", City: "Ulaanbaatar", IPs: []net.IP{{206, 123, 153, 131}}},
{Region: "Asia", Country: "Pakistan", City: "Islamabad", IPs: []net.IP{{104, 250, 187, 3}}},
{Region: "Asia", Country: "Papua New Guinea", City: "Port Moresby", IPs: []net.IP{{206, 123, 155, 131}}},
{Region: "Asia", Country: "Philippines", City: "Manila", IPs: []net.IP{{36, 255, 97, 3}}},
{Region: "Asia", Country: "Sri Lanka", City: "Colombo", IPs: []net.IP{{206, 123, 154, 4}}},
{Region: "Asia", Country: "Taiwan", City: "Taipei", IPs: []net.IP{{203, 69, 105, 5}}},
{Region: "Asia", Country: "Tajikistan", City: "Dushanbe", IPs: []net.IP{{206, 123, 151, 4}}},
{Region: "Asia", Country: "Thailand", City: "Bangkok", IPs: []net.IP{{104, 37, 6, 4}}},
{Region: "Asia", Country: "Turkey", City: "Istanbul", IPs: []net.IP{{82, 102, 22, 212}}},
{Region: "Asia", Country: "Turkmenistan", City: "Ashgabat", IPs: []net.IP{{206, 123, 152, 131}}},
{Region: "Asia", Country: "Uzbekistan", City: "Tashkent", IPs: []net.IP{{206, 123, 150, 131}}},
{Region: "Asia", Country: "Vietnam", City: "Hanoi", IPs: []net.IP{{192, 253, 249, 132}}},
{Region: "Europe", Country: "Albania", City: "Tirane", IPs: []net.IP{{46, 243, 224, 2}}},
{Region: "Europe", Country: "Armenia", City: "Yerevan", IPs: []net.IP{{172, 94, 35, 4}}},
{Region: "Europe", Country: "Austria", City: "Vienna", IPs: []net.IP{{172, 94, 125, 4}}},
{Region: "Europe", Country: "Belgium", City: "Brussels", IPs: []net.IP{{172, 111, 223, 4}, {172, 111, 244, 4}}},
{Region: "Europe", Country: "Bosnia and Herzegovina", City: "Sarajevo", IPs: []net.IP{{104, 250, 169, 122}}},
{Region: "Europe", Country: "Bulgaria", City: "Sofia", IPs: []net.IP{{37, 120, 152, 52}}},
{Region: "Europe", Country: "Croatia", City: "Zagreb", IPs: []net.IP{{104, 250, 163, 2}}},
{Region: "Europe", Country: "Cyprus", City: "Nicosia", IPs: []net.IP{{188, 72, 119, 4}}},
{Region: "Europe", Country: "Denmark", City: "Copenhagen", IPs: []net.IP{{172, 111, 223, 4}}},
{Region: "Europe", Country: "Estonia", City: "Tallinn", IPs: []net.IP{{185, 166, 87, 2}, {188, 72, 111, 4}}},
{Region: "Europe", Country: "France", City: "Paris", IPs: []net.IP{{172, 111, 223, 4}}},
{Region: "Europe", Country: "Georgia", City: "Tbilisi", IPs: []net.IP{{141, 101, 156, 2}}},
{Region: "Europe", Country: "Germany", City: "Frankfurt", IPs: []net.IP{{82, 102, 16, 107}}},
{Region: "Europe", Country: "Germany", City: "Munich", IPs: []net.IP{{82, 102, 16, 107}}},
{Region: "Europe", Country: "Germany", City: "Nuremberg", IPs: []net.IP{{172, 94, 125, 2}}},
{Region: "Europe", Country: "Greece", City: "Thessaloniki", IPs: []net.IP{{5, 172, 199, 2}}},
{Region: "Europe", Country: "Hungary", City: "Budapest", IPs: []net.IP{{217, 138, 192, 136}}},
{Region: "Europe", Country: "Iceland", City: "Reykjavik", IPs: []net.IP{{192, 253, 250, 1}}},
{Region: "Europe", Country: "Ireland", City: "Dublin", IPs: []net.IP{{78, 153, 208, 173}}},
{Region: "Europe", Country: "Isle of Man", City: "Onchan", IPs: []net.IP{{46, 243, 144, 2}}},
{Region: "Europe", Country: "Italy", City: "Milano", IPs: []net.IP{{45, 9, 251, 2}}},
{Region: "Europe", Country: "Latvia", City: "RIGA", IPs: []net.IP{{185, 118, 76, 5}}},
{Region: "Europe", Country: "Liechtenstein", City: "Vaduz", IPs: []net.IP{{104, 250, 164, 4}}},
{Region: "Europe", Country: "Lithuania", City: "Vilnius", IPs: []net.IP{{188, 72, 116, 3}}},
{Region: "Europe", Country: "Luxembourg", City: "Luxembourg", IPs: []net.IP{{94, 242, 225, 132}}},
{Region: "Europe", Country: "Malta", City: "Sliema", IPs: []net.IP{{46, 243, 241, 4}}},
{Region: "Europe", Country: "Monaco", City: "Monaco", IPs: []net.IP{{104, 250, 168, 132}}},
{Region: "Europe", Country: "Montenegro", City: "Podgorica", IPs: []net.IP{{104, 250, 165, 121}}},
{Region: "Europe", Country: "Netherlands", City: "Amsterdam", IPs: []net.IP{{37, 120, 192, 212}}},
{Region: "Europe", Country: "Norway", City: "Oslo", IPs: []net.IP{{82, 102, 22, 212}}},
{Region: "Europe", Country: "Poland", City: "Warsaw", IPs: []net.IP{{5, 253, 206, 251}}},
{Region: "Europe", Country: "Portugal", City: "Lisbon", IPs: []net.IP{{5, 154, 174, 3}}},
{Region: "Europe", Country: "Romania", City: "Bucharest", IPs: []net.IP{{188, 240, 220, 35}}},
{Region: "Europe", Country: "Serbia", City: "Niš", IPs: []net.IP{{152, 89, 160, 201}}},
{Region: "Europe", Country: "Slovakia", City: "Bratislava", IPs: []net.IP{{188, 72, 112, 3}}},
{Region: "Europe", Country: "Slovenia", City: "Ljubljana", IPs: []net.IP{{104, 243, 246, 129}}},
{Region: "Europe", Country: "Spain", City: "Barcelona", IPs: []net.IP{{185, 230, 124, 147}}},
{Region: "Europe", Country: "Sweden", City: "Stockholm", IPs: []net.IP{{45, 74, 46, 2}}},
{Region: "Europe", Country: "Switzerland", City: "Zurich", IPs: []net.IP{{45, 12, 222, 98}, {45, 12, 222, 99}}},
{Region: "Europe", Country: "United Kingdom", City: "Gosport", IPs: []net.IP{{45, 141, 154, 70}}},
{Region: "Europe", Country: "United Kingdom", City: "London", IPs: []net.IP{{193, 9, 113, 66}}},
{Region: "Europe", Country: "United Kingdom", City: "Maidenhead", IPs: []net.IP{{188, 72, 89, 4}}},
{Region: "Europe", Country: "United Kingdom", City: "Manchester", IPs: []net.IP{{172, 111, 183, 2}}},
{Region: "Middle East", Country: "Bahrain", City: "Manama", IPs: []net.IP{{46, 243, 150, 4}}},
{Region: "Middle East", Country: "Jordan", City: "Amman", IPs: []net.IP{{172, 111, 152, 3}}},
{Region: "Middle East", Country: "Kuwait", City: "Kuwait", IPs: []net.IP{{206, 123, 146, 4}}},
{Region: "Middle East", Country: "Oman", City: "Salalah", IPs: []net.IP{{46, 243, 148, 125}}},
{Region: "Middle East", Country: "Qatar", City: "Doha", IPs: []net.IP{{46, 243, 147, 2}}},
{Region: "Middle East", Country: "Saudi Arabia", City: "Jeddah", IPs: []net.IP{{45, 74, 1, 4}}},
{Region: "Middle East", Country: "United Arab Emirates", City: "Dubai", IPs: []net.IP{{104, 37, 6, 4}}},
{Region: "North America", Country: "Aruba", City: "Oranjestad", IPs: []net.IP{{104, 243, 246, 129}}},
{Region: "North America", Country: "Barbados", City: "Bridgetown", IPs: []net.IP{{172, 94, 97, 2}}},
{Region: "North America", Country: "Belize", City: "Belmopan", IPs: []net.IP{{104, 243, 241, 4}}},
{Region: "North America", Country: "Bermuda", City: "Hamilton", IPs: []net.IP{{172, 94, 76, 2}}},
{Region: "North America", Country: "Canada", City: "Montreal", IPs: []net.IP{{172, 94, 7, 2}}},
{Region: "North America", Country: "Canada", City: "Toronto", IPs: []net.IP{{172, 94, 7, 2}}},
{Region: "North America", Country: "Canada", City: "Vancouver", IPs: []net.IP{{172, 94, 34, 4}}},
{Region: "North America", Country: "Cayman Islands", City: "George Town", IPs: []net.IP{{172, 94, 113, 2}}},
{Region: "North America", Country: "Costa Rica", City: "San Jose", IPs: []net.IP{{104, 243, 245, 1}}},
{Region: "North America", Country: "Dominica", City: "Roseau", IPs: []net.IP{{45, 74, 22, 2}}},
{Region: "North America", Country: "Dominican Republic", City: "Santo Domingo", IPs: []net.IP{{45, 74, 23, 129}}},
{Region: "North America", Country: "El Salvador", City: "San Salvador", IPs: []net.IP{{45, 74, 17, 129}}},
{Region: "North America", Country: "Grenada", City: "St George's", IPs: []net.IP{{45, 74, 21, 129}}},
{Region: "North America", Country: "Guatemala", City: "Guatemala", IPs: []net.IP{{45, 74, 17, 129}}},
{Region: "North America", Country: "Haiti", City: "PORT-AU-PRINCE", IPs: []net.IP{{45, 74, 24, 2}}},
{Region: "North America", Country: "Honduras", City: "TEGUCIGALPA", IPs: []net.IP{{45, 74, 18, 2}}},
{Region: "North America", Country: "Jamaica", City: "Kingston", IPs: []net.IP{{104, 250, 182, 126}}},
{Region: "North America", Country: "Mexico", City: "Mexico City", IPs: []net.IP{{104, 243, 243, 131}}},
{Region: "North America", Country: "Montserrat", City: "plymouth", IPs: []net.IP{{45, 74, 26, 190}}},
{Region: "North America", Country: "Puerto Rico", City: "San Juan", IPs: []net.IP{{104, 37, 2, 2}}},
{Region: "North America", Country: "Saint Lucia", City: "Castries", IPs: []net.IP{{45, 74, 23, 2}}},
{Region: "North America", Country: "The Bahamas", City: "Freeport", IPs: []net.IP{{104, 243, 242, 2}}},
{Region: "North America", Country: "Trinidad and Tobago", City: "Port of Spain", IPs: []net.IP{{45, 74, 21, 2}}},
{Region: "North America", Country: "Turks and Caicos Islands", City: "Balfour Town", IPs: []net.IP{{172, 94, 60, 4}}},
{Region: "North America", Country: "United States", City: "Ashburn", IPs: []net.IP{{46, 243, 249, 2}}},
{Region: "North America", Country: "United States", City: "Chicago", IPs: []net.IP{{37, 230, 169, 4}}},
{Region: "North America", Country: "United States", City: "Columbus", IPs: []net.IP{{172, 94, 115, 2}}},
{Region: "North America", Country: "United States", City: "Georgia", IPs: []net.IP{{141, 101, 168, 4}}},
{Region: "North America", Country: "United States", City: "Houston", IPs: []net.IP{{172, 94, 1, 4}}},
{Region: "North America", Country: "United States", City: "Los Angeles", IPs: []net.IP{{172, 111, 147, 4}}},
{Region: "North America", Country: "United States", City: "Miami", IPs: []net.IP{{172, 94, 108, 4}}},
{Region: "North America", Country: "United States", City: "New Jersey", IPs: []net.IP{{141, 101, 168, 4}}},
{Region: "North America", Country: "United States", City: "New York", IPs: []net.IP{{172, 111, 147, 4}}},
{Region: "North America", Country: "United States", City: "Phoenix", IPs: []net.IP{{172, 94, 26, 4}}},
{Region: "North America", Country: "United States", City: "Salt Lake City", IPs: []net.IP{{141, 101, 168, 4}}},
{Region: "North America", Country: "United States", City: "San Francisco", IPs: []net.IP{{172, 111, 147, 4}}},
{Region: "North America", Country: "United States", City: "Seattle", IPs: []net.IP{{172, 94, 86, 2}}},
{Region: "North America", Country: "United States", City: "Washington, D.C.", IPs: []net.IP{{37, 230, 169, 4}}},
{Region: "Oceania", Country: "Australia", City: "Brisbane", IPs: []net.IP{{172, 111, 236, 2}}},
{Region: "Oceania", Country: "Australia", City: "Melbourne", IPs: []net.IP{{118, 127, 62, 2}}},
{Region: "Oceania", Country: "Australia", City: "Perth", IPs: []net.IP{{172, 94, 123, 4}}},
{Region: "Oceania", Country: "Australia", City: "Sydney", IPs: []net.IP{{46, 243, 245, 4}}},
{Region: "Oceania", Country: "New Zealand", City: "Auckland", IPs: []net.IP{{43, 228, 156, 4}}},
{Region: "South America", Country: "Argentina", City: "Buenos Aires", IPs: []net.IP{{104, 243, 244, 1}}},
{Region: "South America", Country: "Bolivia", City: "Sucre", IPs: []net.IP{{172, 94, 77, 2}}},
{Region: "South America", Country: "Brazil", City: "Sao Paulo", IPs: []net.IP{{104, 243, 244, 2}}},
{Region: "South America", Country: "British Virgin Island", City: "Road Town", IPs: []net.IP{{104, 250, 184, 130}}},
{Region: "South America", Country: "Chile", City: "Santiago", IPs: []net.IP{{191, 96, 183, 251}}},
{Region: "South America", Country: "Colombia", City: "Bogota", IPs: []net.IP{{172, 111, 132, 1}}},
{Region: "South America", Country: "Ecuador", City: "Quito", IPs: []net.IP{{104, 250, 180, 126}}},
{Region: "South America", Country: "Guyana", City: "Georgetown", IPs: []net.IP{{45, 74, 20, 129}}},
{Region: "South America", Country: "Panama", City: "Panama City", IPs: []net.IP{{104, 243, 243, 131}}},
{Region: "South America", Country: "Paraguay", City: "Asuncion", IPs: []net.IP{{45, 74, 19, 129}}},
{Region: "South America", Country: "Peru", City: "Lima", IPs: []net.IP{{172, 111, 131, 1}}},
{Region: "South America", Country: "Suriname", City: "Paramaribo", IPs: []net.IP{{45, 74, 20, 4}}},
}
}

View File

@@ -19,6 +19,8 @@ const (
Vyprvpn models.VPNProvider = "vyprvpn"
// NordVPN is a VPN provider
Nordvpn models.VPNProvider = "nordvpn"
// PureVPN is a VPN provider
Purevpn models.VPNProvider = "purevpn"
)
const (

View File

@@ -93,23 +93,59 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
<-ctx.Done()
}
func (l *looper) waitForFirstStart(ctx context.Context) {
for {
select {
case <-l.stop:
l.setEnabled(false)
l.logger.Info("not started yet")
case <-l.restart:
if l.isEnabled() {
return
}
l.logger.Info("not restarting because disabled")
case <-l.start:
l.setEnabled(true)
return
case <-ctx.Done():
return
}
}
}
func (l *looper) waitForSubsequentStart(ctx context.Context, unboundCancel context.CancelFunc) {
if l.isEnabled() {
return
}
for {
// wait for a signal to re-enable
select {
case <-l.stop:
l.logger.Info("already disabled")
case <-l.restart:
if !l.isEnabled() {
l.logger.Info("not restarting because disabled")
} else {
return
}
case <-l.start:
l.setEnabled(true)
return
case <-ctx.Done():
unboundCancel()
return
}
}
}
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
l.fallbackToUnencryptedDNS()
waitForStart := true
for waitForStart {
select {
case <-l.stop:
l.logger.Info("not started yet")
case <-l.restart:
waitForStart = false
case <-l.start:
waitForStart = false
case <-ctx.Done():
l.waitForFirstStart(ctx)
if ctx.Err() != nil {
return
}
}
defer l.logger.Warn("loop exited")
var unboundCtx context.Context
@@ -118,20 +154,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
triggeredRestart := false
l.setEnabled(true)
for ctx.Err() == nil {
for !l.isEnabled() {
// wait for a signal to re-enable
select {
case <-l.stop:
l.logger.Info("already disabled")
case <-l.restart:
l.setEnabled(true)
case <-l.start:
l.setEnabled(true)
case <-ctx.Done():
unboundCancel()
return
}
}
l.waitForSubsequentStart(ctx, unboundCancel)
settings := l.GetSettings()

View File

@@ -62,15 +62,6 @@ func (c *configurator) fallbackToDisabled(ctx context.Context) {
}
func (c *configurator) enable(ctx context.Context) (err error) { //nolint:gocognit
defaultInterface, defaultGateway, err := c.routing.DefaultRoute()
if err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
localSubnet, err := c.routing.LocalSubnet()
if err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err = c.setAllPolicies(ctx, "DROP"); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
@@ -95,50 +86,36 @@ func (c *configurator) enable(ctx context.Context) (err error) { //nolint:gocogn
return fmt.Errorf("cannot enable firewall: %w", err)
}
for _, conn := range c.vpnConnections {
if err = c.acceptOutputTrafficToVPN(ctx, defaultInterface, conn, remove); err != nil {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, conn, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
if err = c.acceptOutputThroughInterface(ctx, string(constants.TUN), remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err := c.acceptInputFromSubnetToSubnet(ctx, "*", localSubnet, localSubnet, remove); err != nil {
if err := c.acceptInputFromSubnetToSubnet(ctx, "*", c.localSubnet, c.localSubnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err := c.acceptOutputFromSubnetToSubnet(ctx, "*", localSubnet, localSubnet, remove); err != nil {
if err := c.acceptOutputFromSubnetToSubnet(ctx, "*", c.localSubnet, c.localSubnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
for _, subnet := range c.allowedSubnets {
if err := c.acceptInputFromSubnetToSubnet(ctx, defaultInterface, subnet, localSubnet, remove); err != nil {
if err := c.acceptInputFromSubnetToSubnet(ctx, c.defaultInterface, subnet, c.localSubnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err := c.acceptOutputFromSubnetToSubnet(ctx, defaultInterface, localSubnet, subnet, remove); err != nil {
if err := c.acceptOutputFromSubnetToSubnet(ctx, c.defaultInterface, c.localSubnet, subnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
// Re-ensure all routes exist
for _, subnet := range c.allowedSubnets {
if err := c.routing.AddRouteVia(ctx, subnet, defaultGateway, defaultInterface); err != nil {
if err := c.routing.AddRouteVia(ctx, subnet, c.defaultGateway, c.defaultInterface); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
for port := range c.allowedPorts {
// TODO restrict interface
if err := c.acceptInputToPort(ctx, "*", constants.TCP, port, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err := c.acceptInputToPort(ctx, "*", constants.UDP, port, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
if c.portForwarded > 0 {
const tun = string(constants.TUN)
if err := c.acceptInputToPort(ctx, tun, constants.TCP, c.portForwarded, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err := c.acceptInputToPort(ctx, tun, constants.UDP, c.portForwarded, remove); err != nil {
for port, intf := range c.allowedInputPorts {
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}

View File

@@ -18,10 +18,11 @@ type Configurator interface {
SetEnabled(ctx context.Context, enabled bool) (err error)
SetVPNConnections(ctx context.Context, connections []models.OpenVPNConnection) (err error)
SetAllowedSubnets(ctx context.Context, subnets []net.IPNet) (err error)
SetAllowedPort(ctx context.Context, port uint16) error
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
SetPortForward(ctx context.Context, port uint16) (err error)
SetDebug()
// SetNetworkInformation is meant to be called only once
SetNetworkInformation(defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet)
}
type configurator struct { //nolint:maligned
@@ -31,13 +32,16 @@ type configurator struct { //nolint:maligned
fileManager files.FileManager // for custom iptables rules
iptablesMutex sync.Mutex
debug bool
defaultInterface string
defaultGateway net.IP
localSubnet net.IPNet
networkInfoMutex sync.Mutex
// State
enabled bool
vpnConnections []models.OpenVPNConnection
allowedSubnets []net.IPNet
allowedPorts map[uint16]struct{}
portForwarded uint16
allowedInputPorts map[uint16]string // port to interface mapping
stateMutex sync.Mutex
}
@@ -48,10 +52,18 @@ func NewConfigurator(logger logging.Logger, routing routing.Routing, fileManager
logger: logger.WithPrefix("firewall: "),
routing: routing,
fileManager: fileManager,
allowedPorts: make(map[uint16]struct{}),
allowedInputPorts: make(map[uint16]string),
}
}
func (c *configurator) SetDebug() {
c.debug = true
}
func (c *configurator) SetNetworkInformation(defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet) {
c.networkInfoMutex.Lock()
defer c.networkInfoMutex.Unlock()
c.defaultInterface = defaultInterface
c.defaultGateway = defaultGateway
c.localSubnet = localSubnet
}

View File

@@ -134,14 +134,15 @@ func (c *configurator) acceptOutputFromSubnetToSubnet(ctx context.Context, intf
}
// Used for port forwarding, with intf set to tun
func (c *configurator) acceptInputToPort(ctx context.Context, intf string, protocol models.NetworkProtocol, port uint16, remove bool) error {
func (c *configurator) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
interfaceFlag := "-i " + intf
if intf == "*" { // all interfaces
interfaceFlag = ""
}
return c.runIptablesInstruction(ctx,
fmt.Sprintf("%s INPUT %s -p %s --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, protocol, port),
)
return c.runIptablesInstructions(ctx, []string{
fmt.Sprintf("%s INPUT %s -p tcp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port),
fmt.Sprintf("%s INPUT %s -p udp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port),
})
}
func (c *configurator) runUserPostRules(ctx context.Context, filepath string, remove bool) error {

View File

@@ -3,11 +3,9 @@ package firewall
import (
"context"
"fmt"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func (c *configurator) SetAllowedPort(ctx context.Context, port uint16) (err error) {
func (c *configurator) SetAllowedPort(ctx context.Context, port uint16, intf string) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
@@ -16,25 +14,28 @@ func (c *configurator) SetAllowedPort(ctx context.Context, port uint16) (err err
}
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal list")
c.allowedPorts[port] = struct{}{}
c.logger.Info("firewall disabled, only updating allowed ports internal state")
c.allowedInputPorts[port] = intf
return nil
}
c.logger.Info("setting allowed port %d through firewall...", port)
c.logger.Info("setting allowed input port %d through interface %s...", port, intf)
if _, ok := c.allowedPorts[port]; ok {
if existingIntf, ok := c.allowedInputPorts[port]; ok {
if intf == existingIntf {
return nil
}
const remove = true
if err := c.acceptInputToPort(ctx, existingIntf, port, remove); err != nil {
return fmt.Errorf("cannot remove old allowed port %d through interface %s: %w", port, existingIntf, err)
}
}
const remove = false
if err := c.acceptInputToPort(ctx, "*", constants.TCP, port, remove); err != nil {
return fmt.Errorf("cannot set allowed port %d through firewall: %w", port, err)
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot set allowed port %d through interface %s: %w", port, intf, err)
}
if err := c.acceptInputToPort(ctx, "*", constants.UDP, port, remove); err != nil {
return fmt.Errorf("cannot set allowed port %d through firewall: %w", port, err)
}
c.allowedPorts[port] = struct{}{}
c.allowedInputPorts[port] = intf
return nil
}
@@ -49,63 +50,22 @@ func (c *configurator) RemoveAllowedPort(ctx context.Context, port uint16) (err
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal list")
delete(c.allowedPorts, port)
delete(c.allowedInputPorts, port)
return nil
}
c.logger.Info("removing allowed port %d through firewall...", port)
if _, ok := c.allowedPorts[port]; !ok {
intf, ok := c.allowedInputPorts[port]
if !ok {
return nil
}
const remove = true
if err := c.acceptInputToPort(ctx, "*", constants.TCP, port, remove); err != nil {
return fmt.Errorf("cannot remove allowed port %d through firewall: %w", port, err)
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot remove allowed port %d through interface %s: %w", port, intf, err)
}
if err := c.acceptInputToPort(ctx, "*", constants.UDP, port, remove); err != nil {
return fmt.Errorf("cannot remove allowed port %d through firewall: %w", port, err)
}
delete(c.allowedPorts, port)
delete(c.allowedInputPorts, port)
return nil
}
// Use 0 to remove
func (c *configurator) SetPortForward(ctx context.Context, port uint16) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
if port == c.portForwarded {
return nil
}
if !c.enabled {
c.logger.Info("firewall disabled, only updating port forwarded internally")
c.portForwarded = port
return nil
}
const tun = string(constants.TUN)
if c.portForwarded > 0 {
if err := c.acceptInputToPort(ctx, tun, constants.TCP, c.portForwarded, true); err != nil {
return fmt.Errorf("cannot remove outdated port forward rule from firewall: %w", err)
}
if err := c.acceptInputToPort(ctx, tun, constants.UDP, c.portForwarded, true); err != nil {
return fmt.Errorf("cannot remove outdated port forward rule from firewall: %w", err)
}
}
if port == 0 { // not changing port
c.portForwarded = 0
return nil
}
if err := c.acceptInputToPort(ctx, tun, constants.TCP, port, false); err != nil {
return fmt.Errorf("cannot accept port forwarded through firewall: %w", err)
}
if err := c.acceptInputToPort(ctx, tun, constants.UDP, port, false); err != nil {
return fmt.Errorf("cannot accept port forwarded through firewall: %w", err)
}
return nil
}

View File

@@ -12,9 +12,7 @@ func (c *configurator) SetAllowedSubnets(ctx context.Context, subnets []net.IPNe
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed subnets internal list and updating routes")
if err := c.updateSubnetRoutes(ctx, c.allowedSubnets, subnets); err != nil {
return err
}
c.updateSubnetRoutes(ctx, c.allowedSubnets, subnets)
c.allowedSubnets = make([]net.IPNet, len(subnets))
copy(c.allowedSubnets, subnets)
return nil
@@ -28,17 +26,8 @@ func (c *configurator) SetAllowedSubnets(ctx context.Context, subnets []net.IPNe
return nil
}
defaultInterface, defaultGateway, err := c.routing.DefaultRoute()
if err != nil {
return fmt.Errorf("cannot set allowed subnets through firewall: %w", err)
}
localSubnet, err := c.routing.LocalSubnet()
if err != nil {
return fmt.Errorf("cannot set allowed subnets through firewall: %w", err)
}
c.removeSubnets(ctx, subnetsToRemove, defaultInterface, localSubnet)
if err := c.addSubnets(ctx, subnetsToAdd, defaultInterface, defaultGateway, localSubnet); err != nil {
c.removeSubnets(ctx, subnetsToRemove, c.defaultInterface, c.localSubnet)
if err := c.addSubnets(ctx, subnetsToAdd, c.defaultInterface, c.defaultGateway, c.localSubnet); err != nil {
return fmt.Errorf("cannot set allowed subnets through firewall: %w", err)
}
@@ -135,15 +124,12 @@ func (c *configurator) addSubnets(ctx context.Context, subnets []net.IPNet, defa
return nil
}
func (c *configurator) updateSubnetRoutes(ctx context.Context, oldSubnets, newSubnets []net.IPNet) error {
// updateSubnetRoutes does not return an error in order to try to run as many route commands as possible
func (c *configurator) updateSubnetRoutes(ctx context.Context, oldSubnets, newSubnets []net.IPNet) {
subnetsToAdd := findSubnetsToAdd(oldSubnets, newSubnets)
subnetsToRemove := findSubnetsToRemove(oldSubnets, newSubnets)
if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 {
return nil
}
defaultInterface, defaultGateway, err := c.routing.DefaultRoute()
if err != nil {
return err
return
}
for _, subnet := range subnetsToRemove {
if err := c.routing.DeleteRouteVia(ctx, subnet); err != nil {
@@ -151,9 +137,8 @@ func (c *configurator) updateSubnetRoutes(ctx context.Context, oldSubnets, newSu
}
}
for _, subnet := range subnetsToAdd {
if err := c.routing.AddRouteVia(ctx, subnet, defaultGateway, defaultInterface); err != nil {
if err := c.routing.AddRouteVia(ctx, subnet, c.defaultGateway, c.defaultInterface); err != nil {
c.logger.Error("cannot add route for subnet: %s", err)
}
}
return nil
}

View File

@@ -26,13 +26,8 @@ func (c *configurator) SetVPNConnections(ctx context.Context, connections []mode
return nil
}
defaultInterface, _, err := c.routing.DefaultRoute()
if err != nil {
return fmt.Errorf("cannot set VPN connections through firewall: %w", err)
}
c.removeConnections(ctx, connectionsToRemove, defaultInterface)
if err := c.addConnections(ctx, connectionsToAdd, defaultInterface); err != nil {
c.removeConnections(ctx, connectionsToRemove, c.defaultInterface)
if err := c.addConnections(ctx, connectionsToAdd, c.defaultInterface); err != nil {
return fmt.Errorf("cannot set VPN connections through firewall: %w", err)
}

View File

@@ -1,5 +1,10 @@
package models
import (
"fmt"
"strings"
)
type (
// VPNDevice is the device name used to tunnel using Openvpn
VPNDevice string
@@ -14,7 +19,45 @@ type (
// TinyProxyLogLevel is the log level for TinyProxy
TinyProxyLogLevel string
// VPNProvider is the name of the VPN provider to be used
VPNProvider string // TODO
VPNProvider string
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers
NetworkProtocol string
)
func marshalJSONString(s string) (data []byte, err error) {
return []byte(fmt.Sprintf("%q", s)), nil
}
func unmarshalJSONString(data []byte) (s string) {
s = string(data)
s = strings.TrimPrefix(s, "\"")
s = strings.TrimSuffix(s, "\"")
return s
}
func (v *VPNProvider) MarshalJSON() ([]byte, error) {
return marshalJSONString(string(*v))
}
func (v *VPNProvider) UnmarshalJSON(data []byte) error {
*v = VPNProvider(unmarshalJSONString(data))
return nil
}
func (n *NetworkProtocol) MarshalJSON() ([]byte, error) {
return marshalJSONString(string(*n))
}
func (n *NetworkProtocol) UnmarshalJSON(data []byte) error {
*n = NetworkProtocol(unmarshalJSONString(data))
return nil
}
func (f *Filepath) MarshalJSON() ([]byte, error) {
return marshalJSONString(string(*f))
}
func (f *Filepath) UnmarshalJSON(data []byte) error {
*f = Filepath(unmarshalJSONString(data))
return nil
}

View File

@@ -0,0 +1,41 @@
package models
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_VPNProvider_JSON(t *testing.T) {
t.Parallel()
v := VPNProvider("name")
data, err := v.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, []byte{0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22}, data)
err = v.UnmarshalJSON(data)
require.NoError(t, err)
assert.Equal(t, VPNProvider("name"), v)
}
func Test_NetworkProtocol_JSON(t *testing.T) {
t.Parallel()
v := NetworkProtocol("name")
data, err := v.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, []byte{0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22}, data)
err = v.UnmarshalJSON(data)
require.NoError(t, err)
assert.Equal(t, NetworkProtocol("name"), v)
}
func Test_Filepath_JSON(t *testing.T) {
t.Parallel()
v := Filepath("name")
data, err := v.MarshalJSON()
require.NoError(t, err)
assert.Equal(t, []byte{0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22}, data)
err = v.UnmarshalJSON(data)
require.NoError(t, err)
assert.Equal(t, Filepath("name"), v)
}

View File

@@ -8,48 +8,50 @@ import (
// ProviderSettings contains settings specific to a VPN provider
type ProviderSettings struct {
Name VPNProvider
ServerSelection ServerSelection
ExtraConfigOptions ExtraConfigOptions
PortForwarding PortForwarding
Name VPNProvider `json:"name"`
ServerSelection ServerSelection `json:"serverSelection"`
ExtraConfigOptions ExtraConfigOptions `json:"extraConfig"`
PortForwarding PortForwarding `json:"portForwarding"`
}
type ServerSelection struct { //nolint:maligned
// Common
Protocol NetworkProtocol
TargetIP net.IP
Protocol NetworkProtocol `json:"networkProtocol"`
TargetIP net.IP `json:"targetIP,omitempty"`
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
Region string
Region string `json:"region"`
// Cyberghost
Group string
Group string `json:"group"`
// Mullvad, PureVPN
Country string `json:"country"`
City string `json:"city"`
// Mullvad
Country string
City string
ISP string
Owned bool
ISP string `json:"isp"`
Owned bool `json:"owned"`
// Mullvad, Windscribe
CustomPort uint16
// PIA
EncryptionPreset string
CustomPort uint16 `json:"customPort"`
// NordVPN
Number uint16
Number uint16 `json:"number"`
// PIA
EncryptionPreset string `json:"encryptionPreset"`
}
type ExtraConfigOptions struct {
ClientKey string // Cyberghost
EncryptionPreset string // PIA
ClientKey string `json:"-"` // Cyberghost
EncryptionPreset string `json:"encryptionPreset"` // PIA
}
// PortForwarding contains settings for port forwarding
type PortForwarding struct {
Enabled bool
Filepath Filepath
Enabled bool `json:"enabled"`
Filepath Filepath `json:"filepath"`
}
func (p *PortForwarding) String() string {
@@ -110,6 +112,12 @@ func (p *ProviderSettings) String() string {
"Region: "+p.ServerSelection.Region,
"Number: "+number,
)
case "purevpn":
settingsList = append(settingsList,
"Region: "+p.ServerSelection.Region,
"Country: "+p.ServerSelection.Country,
"City: "+p.ServerSelection.City,
)
default:
settingsList = append(settingsList,
"<Missing String method, please implement me!>",

View File

@@ -44,3 +44,10 @@ type NordvpnServer struct { //nolint:maligned
TCP bool
UDP bool
}
type PurevpnServer struct {
Region string
Country string
City string
IPs []net.IP
}

View File

@@ -23,6 +23,7 @@ type Looper interface {
PortForward()
GetSettings() (settings settings.OpenVPN)
SetSettings(settings settings.OpenVPN)
GetPortForwarded() (portForwarded uint16)
}
type looper struct {
@@ -30,6 +31,8 @@ type looper struct {
provider models.VPNProvider
settings settings.OpenVPN
settingsMutex sync.RWMutex
portForwarded uint16
portForwardedMutex sync.RWMutex
// Fixed parameters
uid int
gid int
@@ -98,7 +101,10 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
settings := l.GetSettings()
providerConf := provider.New(l.provider)
connections, err := providerConf.GetOpenVPNConnections(settings.Provider.ServerSelection)
if err != nil {
l.fatalOnError(err)
return
}
lines := providerConf.BuildConf(
connections,
settings.Verbosity,
@@ -109,14 +115,19 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
settings.Auth,
settings.Provider.ExtraConfigOptions,
)
err = l.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.Ownership(l.uid, l.gid), files.Permissions(0400))
if err := l.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.Ownership(l.uid, l.gid), files.Permissions(0400)); err != nil {
l.fatalOnError(err)
return
}
err = l.conf.WriteAuthFile(settings.User, settings.Password, l.uid, l.gid)
if err := l.conf.WriteAuthFile(settings.User, settings.Password, l.uid, l.gid); err != nil {
l.fatalOnError(err)
return
}
if err := l.fw.SetVPNConnections(ctx, connections); err != nil {
l.fatalOnError(err)
return
}
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
@@ -187,10 +198,19 @@ func (l *looper) portForward(ctx context.Context, providerConf provider.Provider
port, err = providerConf.GetPortForward(client)
if err != nil {
l.logAndWait(ctx, err)
continue
}
}
l.logger.Info("port forwarded is %d", port)
l.portForwardedMutex.Lock()
if err := l.fw.RemoveAllowedPort(ctx, l.portForwarded); err != nil {
l.logger.Error(err)
}
if err := l.fw.SetAllowedPort(ctx, port, string(constants.TUN)); err != nil {
l.logger.Error(err)
}
l.portForwarded = port
l.portForwardedMutex.Unlock()
filepath := settings.Provider.PortForwarding.Filepath
l.logger.Info("writing forwarded port to %s", filepath)
@@ -201,8 +221,10 @@ func (l *looper) portForward(ctx context.Context, providerConf provider.Provider
if err != nil {
l.logger.Error(err)
}
}
if err := l.fw.SetPortForward(ctx, port); err != nil {
l.logger.Error(err)
}
func (l *looper) GetPortForwarded() (portForwarded uint16) {
l.portForwardedMutex.RLock()
defer l.portForwardedMutex.RUnlock()
return l.portForwarded
}

View File

@@ -10,14 +10,15 @@ import (
// GetCyberghostGroup obtains the server group for the Cyberghost server from the
// environment variable CYBERGHOST_GROUP
func (p *reader) GetCyberghostGroup() (group string, err error) {
s, err := p.envParams.GetValueIfInside("CYBERGHOST_GROUP", constants.CyberghostGroupChoices())
s, err := p.envParams.GetValueIfInside("CYBERGHOST_GROUP", constants.CyberghostGroupChoices(), libparams.Default("Premium UDP Europe"))
return s, err
}
// GetCyberghostRegion obtains the country name for the Cyberghost server from the
// environment variable REGION
func (p *reader) GetCyberghostRegion() (region string, err error) {
s, err := p.envParams.GetValueIfInside("REGION", constants.CyberghostRegionChoices())
choices := append(constants.CyberghostRegionChoices(), "")
s, err := p.envParams.GetValueIfInside("REGION", choices)
return s, err
}

View File

@@ -3,6 +3,7 @@ package params
import (
"fmt"
"net"
"strconv"
"strings"
libparams "github.com/qdm12/golibs/params"
@@ -35,6 +36,30 @@ func (r *reader) GetExtraSubnets() (extraSubnets []net.IPNet, err error) {
return extraSubnets, nil
}
// GetAllowedVPNInputPorts obtains a list of input ports to allow from the
// VPN server side in the firewall, from the environment variable FIREWALL_VPN_INPUT_PORTS
func (r *reader) GetVPNInputPorts() (ports []uint16, err error) {
s, err := r.envParams.GetEnv("FIREWALL_VPN_INPUT_PORTS", libparams.Default(""))
if err != nil {
return nil, err
}
if len(s) == 0 {
return nil, nil
}
portsStr := strings.Split(s, ",")
ports = make([]uint16, len(portsStr))
for i := range portsStr {
portInt, err := strconv.Atoi(portsStr[i])
if err != nil {
return nil, fmt.Errorf("VPN input port %q is not valid (%s)", portInt, err)
} else if portInt <= 0 || portInt > 65535 {
return nil, fmt.Errorf("VPN input port %d must be between 1 and 65535", portInt)
}
ports[i] = uint16(portInt)
}
return ports, nil
}
// GetFirewallDebug obtains if the firewall should run in debug verbose mode from the environment variable FIREWALL_DEBUG
func (r *reader) GetFirewallDebug() (debug bool, err error) {
return r.envParams.GetOnOff("FIREWALL_DEBUG", libparams.Default("off"))

View File

@@ -8,7 +8,8 @@ import (
// GetNordvpnRegion obtains the region (country) for the NordVPN server from the
// environment variable REGION
func (r *reader) GetNordvpnRegion() (region string, err error) {
return r.envParams.GetValueIfInside("REGION", constants.NordvpnRegionChoices())
choices := append(constants.NordvpnRegionChoices(), "")
return r.envParams.GetValueIfInside("REGION", choices)
}
// GetNordvpnRegion obtains the server number (optional) for the NordVPN server from the

View File

@@ -42,6 +42,7 @@ type Reader interface {
// Firewall getters
GetFirewall() (enabled bool, err error)
GetExtraSubnets() (extraSubnets []net.IPNet, err error)
GetVPNInputPorts() (ports []uint16, err error)
GetFirewallDebug() (debug bool, err error)
// VPN getters
@@ -85,6 +86,11 @@ type Reader interface {
GetNordvpnRegion() (region string, err error)
GetNordvpnNumber() (number uint16, err error)
// PureVPN getters
GetPurevpnRegion() (region string, err error)
GetPurevpnCountry() (country string, err error)
GetPurevpnCity() (city string, err error)
// Shadowsocks getters
GetShadowSocks() (activated bool, err error)
GetShadowSocksLog() (activated bool, err error)
@@ -130,7 +136,7 @@ func NewReader(logger logging.Logger, fileManager files.FileManager) Reader {
// GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP
func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) {
s, err := r.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost", "vyprvpn", "nordvpn"})
s, err := r.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost", "vyprvpn", "nordvpn", "purevpn"})
if s == "pia" {
s = "private internet access"
}

View File

@@ -2,7 +2,6 @@ package params
import (
"fmt"
"math/rand"
libparams "github.com/qdm12/golibs/params"
"github.com/qdm12/private-internet-access-docker/internal/constants"
@@ -60,9 +59,5 @@ func (r *reader) GetPIAEncryptionPreset() (preset string, err error) {
// environment variable REGION
func (r *reader) GetPIARegion() (region string, err error) {
choices := append(constants.PIAGeoChoices(), "")
s, err := r.envParams.GetValueIfInside("REGION", choices)
if len(s) == 0 { // Suggestion by @rorph https://github.com/rorph
s = choices[rand.Int()%len(choices)] //nolint:gosec
}
return s, err
return r.envParams.GetValueIfInside("REGION", choices)
}

View File

@@ -0,0 +1,26 @@
package params
import (
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
// GetPurevpnRegion obtains the region (continent) for the PureVPN server from the
// environment variable REGION
func (r *reader) GetPurevpnRegion() (region string, err error) {
choices := append(constants.PurevpnRegionChoices(), "")
return r.envParams.GetValueIfInside("REGION", choices)
}
// GetPurevpnCountry obtains the country for the PureVPN server from the
// environment variable COUNTRY
func (r *reader) GetPurevpnCountry() (country string, err error) {
choices := append(constants.PurevpnCountryChoices(), "")
return r.envParams.GetValueIfInside("COUNTRY", choices)
}
// GetPurevpnCity obtains the city for the PureVPN server from the
// environment variable CITY
func (r *reader) GetPurevpnCity() (city string, err error) {
choices := append(constants.PurevpnCityChoices(), "")
return r.envParams.GetValueIfInside("CITY", choices)
}

View File

@@ -7,6 +7,6 @@ import (
// GetSurfsharkRegion obtains the region for the Surfshark server from the
// environment variable REGION
func (r *reader) GetSurfsharkRegion() (region string, err error) {
s, err := r.envParams.GetValueIfInside("REGION", constants.SurfsharkRegionChoices())
return s, err
choices := append(constants.SurfsharkRegionChoices(), "")
return r.envParams.GetValueIfInside("REGION", choices)
}

View File

@@ -7,5 +7,6 @@ import (
// GetVyprvpnRegion obtains the region for the Vyprvpn server from the
// environment variable REGION
func (r *reader) GetVyprvpnRegion() (region string, err error) {
return r.envParams.GetValueIfInside("REGION", constants.VyprvpnRegionChoices())
choices := append(constants.VyprvpnRegionChoices(), "")
return r.envParams.GetValueIfInside("REGION", choices)
}

View File

@@ -11,8 +11,8 @@ import (
// GetWindscribeRegion obtains the region for the Windscribe server from the
// environment variable REGION
func (r *reader) GetWindscribeRegion() (region string, err error) {
s, err := r.envParams.GetValueIfInside("REGION", constants.WindscribeRegionChoices())
return s, err
choices := append(constants.WindscribeRegionChoices(), "")
return r.envParams.GetValueIfInside("REGION", choices)
}
// GetMullvadPort obtains the port to reach the Mullvad server on from the

View File

@@ -2,7 +2,6 @@ package provider
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/network"
@@ -16,32 +15,48 @@ func newCyberghost() *cyberghost {
return &cyberghost{}
}
func (c *cyberghost) filterServers(region, group string) (servers []models.CyberghostServer) {
allServers := constants.CyberghostServers()
for i, server := range allServers {
if len(region) == 0 {
server.Region = ""
}
if len(group) == 0 {
server.Group = ""
}
if strings.EqualFold(server.Region, region) && strings.EqualFold(server.Group, group) {
servers = append(servers, allServers[i])
}
}
return servers
}
func (c *cyberghost) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
var IPs []net.IP
for _, server := range constants.CyberghostServers() {
if strings.EqualFold(server.Region, selection.Region) && strings.EqualFold(server.Group, selection.Group) {
IPs = server.IPs
}
}
if len(IPs) == 0 {
return nil, fmt.Errorf("no IP found for group %q and region %q", selection.Group, selection.Region)
servers := c.filterServers(selection.Region, selection.Group)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group)
}
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
found := false
for i := range IPs {
if IPs[i].Equal(selection.TargetIP) {
found = true
break
if selection.TargetIP.Equal(IP) {
return []models.OpenVPNConnection{{IP: IP, Port: 443, Protocol: selection.Protocol}}, nil
}
}
if !found {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
IPs = []net.IP{selection.TargetIP}
}
for _, IP := range IPs {
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: 443, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}

View File

@@ -2,6 +2,7 @@ package provider
import (
"fmt"
"strings"
"github.com/qdm12/golibs/network"
"github.com/qdm12/private-internet-access-docker/internal/constants"
@@ -14,11 +15,33 @@ func newMullvad() *mullvad {
return &mullvad{}
}
func (m *mullvad) filterServers(country, city, isp string) (servers []models.MullvadServer) {
allServers := constants.MullvadServers()
for i, server := range allServers {
if len(country) == 0 {
server.Country = ""
}
if len(city) == 0 {
server.City = ""
}
if len(isp) == 0 {
server.ISP = ""
}
if strings.EqualFold(server.Country, country) &&
strings.EqualFold(server.City, city) &&
strings.EqualFold(server.ISP, isp) {
servers = append(servers, allServers[i])
}
}
return servers
}
func (m *mullvad) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := constants.MullvadServerFilter(selection.Country, selection.City, selection.ISP)
servers := m.filterServers(selection.Country, selection.City, selection.ISP)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for country %q, city %q and ISP %q", selection.Country, selection.City, selection.ISP)
}
for _, server := range servers {
port := server.DefaultPort
if selection.CustomPort > 0 {
@@ -34,9 +57,15 @@ func (m *mullvad) GetOpenVPNConnections(selection models.ServerSelection) (conne
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}

View File

@@ -2,7 +2,6 @@ package provider
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/network"
@@ -16,60 +15,34 @@ func newNordvpn() *nordvpn {
return &nordvpn{}
}
func findServers(selection models.ServerSelection) (servers []models.NordvpnServer) {
for _, server := range constants.NordvpnServers() {
if strings.EqualFold(server.Region, selection.Region) {
if (selection.Protocol == constants.TCP && !server.TCP) || (selection.Protocol == constants.UDP && !server.UDP) {
func (n *nordvpn) filterServers(region string, protocol models.NetworkProtocol, number uint16) (servers []models.NordvpnServer) {
allServers := constants.NordvpnServers()
for i, server := range allServers {
if len(region) == 0 {
server.Region = ""
}
if number == 0 {
server.Number = 0
}
if protocol == constants.TCP && !server.TCP {
continue
} else if protocol == constants.UDP && !server.UDP {
continue
}
if selection.Number > 0 && server.Number == selection.Number {
return []models.NordvpnServer{server}
}
servers = append(servers, server)
if strings.EqualFold(server.Region, region) && server.Number == number {
servers = append(servers, allServers[i])
}
}
return servers
}
func extractIPsFromServers(servers []models.NordvpnServer) (ips []net.IP) {
ips = make([]net.IP, len(servers))
for i := range servers {
ips[i] = servers[i].IP
}
return ips
}
func targetIPInIps(targetIP net.IP, ips []net.IP) error {
for i := range ips {
if targetIP.Equal(ips[i]) {
return nil
}
}
ipsString := make([]string, len(ips))
for i := range ips {
ipsString[i] = ips[i].String()
}
return fmt.Errorf("target IP address %s not found in IP addresses %s", targetIP, strings.Join(ipsString, ", "))
}
func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
servers := findServers(selection)
ips := extractIPsFromServers(servers)
if len(ips) == 0 {
if selection.Number > 0 {
return nil, fmt.Errorf("no IP found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number)
}
return nil, fmt.Errorf("no IP found for region %q, protocol %s", selection.Region, selection.Protocol)
}
var IP net.IP
if selection.TargetIP != nil {
if err := targetIPInIps(selection.TargetIP, ips); err != nil {
return nil, err
}
IP = selection.TargetIP
} else {
IP = ips[0]
servers := n.filterServers(selection.Region, selection.Protocol, selection.Number)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number)
}
var port uint16
switch {
case selection.Protocol == constants.UDP:
@@ -79,7 +52,26 @@ func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
for _, server := range servers {
if selection.TargetIP != nil {
if selection.TargetIP.Equal(server.IP) {
return []models.OpenVPNConnection{{IP: server.IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: server.IP, Port: port, Protocol: selection.Protocol})
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}
func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
@@ -97,7 +89,6 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
"remote-cert-tls server",
// Nordvpn specific
"resolv-retry infinite",
"tun-mtu 1500",
"tun-mtu-extra 32",
"mssfix 1450",

View File

@@ -4,7 +4,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
@@ -24,29 +23,24 @@ func newPrivateInternetAccess() *pia {
}
}
func (p *pia) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
var IPs []net.IP
func (p *pia) filterServers(region string) (servers []models.PIAServer) {
if len(region) == 0 {
return constants.PIAServers()
}
for _, server := range constants.PIAServers() {
if strings.EqualFold(server.Region, selection.Region) {
IPs = server.IPs
if strings.EqualFold(server.Region, region) {
return []models.PIAServer{server}
}
}
if len(IPs) == 0 {
return nil, fmt.Errorf("no IP found for region %q", selection.Region)
return nil
}
if selection.TargetIP != nil {
found := false
for i := range IPs {
if IPs[i].Equal(selection.TargetIP) {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
IPs = []net.IP{selection.TargetIP}
func (p *pia) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := p.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
var port uint16
switch selection.Protocol {
case constants.TCP:
@@ -67,9 +61,27 @@ func (p *pia) GetOpenVPNConnections(selection models.ServerSelection) (connectio
if port == 0 {
return nil, fmt.Errorf("combination of protocol %q and encryption %q does not yield any port number", selection.Protocol, selection.EncryptionPreset)
}
for _, IP := range IPs {
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
if selection.TargetIP.Equal(IP) {
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}

View File

@@ -29,6 +29,8 @@ func New(provider models.VPNProvider) Provider {
return newVyprvpn()
case constants.Nordvpn:
return newNordvpn()
case constants.Purevpn:
return newPurevpn()
default:
return nil // should never occur
}

View File

@@ -0,0 +1,159 @@
package provider
import (
"fmt"
"strings"
"github.com/qdm12/golibs/network"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
type purevpn struct{}
func newPurevpn() *purevpn {
return &purevpn{}
}
func (p *purevpn) filterServers(region, country, city string) (servers []models.PurevpnServer) {
allServers := constants.PurevpnServers()
for i, server := range allServers {
if len(region) == 0 {
server.Region = ""
}
if len(country) == 0 {
server.Country = ""
}
if len(city) == 0 {
server.City = ""
}
if strings.EqualFold(server.Region, region) &&
strings.EqualFold(server.Country, country) &&
strings.EqualFold(server.City, city) {
servers = append(servers, allServers[i])
}
}
return servers
}
func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
servers := p.filterServers(selection.Region, selection.Country, selection.City)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q, country %q and city %q", selection.Region, selection.Country, selection.City)
}
var port uint16
switch {
case selection.Protocol == constants.UDP:
port = 53
case selection.Protocol == constants.TCP:
port = 80
default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
if IP.Equal(selection.TargetIP) {
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}
func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
if len(cipher) == 0 {
cipher = aes256cbc
}
lines = []string{
"client",
"dev tun",
"nobind",
"persist-key",
"remote-cert-tls server",
// Purevpn specific
"key-direction 1",
"remote-cert-tls server",
"cipher AES-256-CBC",
"route-method exe",
"route-delay 0",
"route 0.0.0.0 0.0.0.0",
"script-security 2",
// Added constant values
"auth-nocache",
"mute-replay-warnings",
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
"auth-retry nointeract",
"remote-random",
"suppress-timestamps",
// Modified variables
fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", string(connections[0].Protocol)),
fmt.Sprintf("cipher %s", cipher),
}
if !root {
lines = append(lines, "user nonrootuser")
}
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port))
}
lines = append(lines, []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
constants.PurevpnCertificateAuthority,
"-----END CERTIFICATE-----",
"</ca>",
}...)
lines = append(lines, []string{
"<cert>",
"-----BEGIN CERTIFICATE-----",
constants.PurevpnCertificate,
"-----END CERTIFICATE-----",
"</cert>",
}...)
lines = append(lines, []string{
"<key>",
"-----BEGIN PRIVATE KEY-----",
constants.PurevpnKey,
"-----END PRIVATE KEY-----",
"</key>",
"",
}...)
lines = append(lines, []string{
"<tls-auth>",
"-----BEGIN OpenVPN Static key V1-----",
constants.PurevpnOpenvpnStaticKeyV1,
"-----END OpenVPN Static key V1-----",
"</tls-auth>",
"",
}...)
if len(auth) > 0 {
lines = append(lines, "auth "+auth)
}
if connections[0].Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")
}
return lines
}
func (p *purevpn) GetPortForward(client network.Client) (port uint16, err error) {
panic("port forwarding is not supported for purevpn")
}

View File

@@ -2,7 +2,6 @@ package provider
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/network"
@@ -16,29 +15,24 @@ func newSurfshark() *surfshark {
return &surfshark{}
}
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
var IPs []net.IP
func (s *surfshark) filterServers(region string) (servers []models.SurfsharkServer) {
if len(region) == 0 {
return constants.SurfsharkServers()
}
for _, server := range constants.SurfsharkServers() {
if strings.EqualFold(server.Region, selection.Region) {
IPs = server.IPs
if strings.EqualFold(server.Region, region) {
return []models.SurfsharkServer{server}
}
}
if len(IPs) == 0 {
return nil, fmt.Errorf("no IP found for region %q", selection.Region)
return nil
}
if selection.TargetIP != nil {
found := false
for i := range IPs {
if IPs[i].Equal(selection.TargetIP) {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
IPs = []net.IP{selection.TargetIP}
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
servers := s.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
var port uint16
switch {
case selection.Protocol == constants.TCP:
@@ -48,9 +42,27 @@ func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (con
default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
for _, IP := range IPs {
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
if selection.TargetIP.Equal(IP) {
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}

View File

@@ -2,7 +2,6 @@ package provider
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/network"
@@ -16,29 +15,24 @@ func newVyprvpn() *vyprvpn {
return &vyprvpn{}
}
func (s *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
var IPs []net.IP
func (v *vyprvpn) filterServers(region string) (servers []models.VyprvpnServer) {
if len(region) == 0 {
return constants.VyprvpnServers()
}
for _, server := range constants.VyprvpnServers() {
if strings.EqualFold(server.Region, selection.Region) {
IPs = server.IPs
if strings.EqualFold(server.Region, region) {
return []models.VyprvpnServer{server}
}
}
if len(IPs) == 0 {
return nil, fmt.Errorf("no IP found for region %q", selection.Region)
return nil
}
if selection.TargetIP != nil {
found := false
for i := range IPs {
if IPs[i].Equal(selection.TargetIP) {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
IPs = []net.IP{selection.TargetIP}
func (v *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := v.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
var port uint16
switch {
case selection.Protocol == constants.TCP:
@@ -48,13 +42,31 @@ func (s *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
for _, IP := range IPs {
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
if selection.TargetIP.Equal(IP) {
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}
func (s *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
if len(cipher) == 0 {
cipher = aes256cbc
}
@@ -105,6 +117,6 @@ func (s *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines
}
func (s *vyprvpn) GetPortForward(client network.Client) (port uint16, err error) {
func (v *vyprvpn) GetPortForward(client network.Client) (port uint16, err error) {
panic("port forwarding is not supported for vyprvpn")
}

View File

@@ -2,7 +2,6 @@ package provider
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/network"
@@ -16,29 +15,24 @@ func newWindscribe() *windscribe {
return &windscribe{}
}
func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
var IPs []net.IP
func (w *windscribe) filterServers(region string) (servers []models.WindscribeServer) {
if len(region) == 0 {
return constants.WindscribeServers()
}
for _, server := range constants.WindscribeServers() {
if strings.EqualFold(server.Region, selection.Region) {
IPs = server.IPs
if strings.EqualFold(server.Region, region) {
return []models.WindscribeServer{server}
}
}
if len(IPs) == 0 {
return nil, fmt.Errorf("no IP found for region %q", selection.Region)
return nil
}
if selection.TargetIP != nil {
found := false
for i := range IPs {
if IPs[i].Equal(selection.TargetIP) {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
IPs = []net.IP{selection.TargetIP}
func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := w.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
var port uint16
switch {
case selection.CustomPort > 0:
@@ -50,9 +44,27 @@ func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (co
default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
for _, IP := range IPs {
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
if selection.TargetIP.Equal(IP) {
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
}

View File

@@ -0,0 +1,36 @@
package server
import (
"encoding/json"
"net/http"
)
func (s *server) handleGetPortForwarded(w http.ResponseWriter) {
port := s.getPortForwarded()
data, err := json.Marshal(struct {
Port uint16 `json:"port"`
}{port})
if err != nil {
s.logger.Warn(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if _, err := w.Write(data); err != nil {
s.logger.Warn(err)
w.WriteHeader(http.StatusInternalServerError)
}
}
func (s *server) handleGetOpenvpnSettings(w http.ResponseWriter) {
settings := s.getOpenvpnSettings()
data, err := json.Marshal(settings)
if err != nil {
s.logger.Warn(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if _, err := w.Write(data); err != nil {
s.logger.Warn(err)
w.WriteHeader(http.StatusInternalServerError)
}
}

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/private-internet-access-docker/internal/settings"
)
type Server interface {
@@ -19,14 +20,19 @@ type server struct {
logger logging.Logger
restartOpenvpn func()
restartUnbound func()
getOpenvpnSettings func() settings.OpenVPN
getPortForwarded func() uint16
}
func New(address string, logger logging.Logger, restartOpenvpn, restartUnbound func()) Server {
func New(address string, logger logging.Logger, restartOpenvpn, restartUnbound func(),
getOpenvpnSettings func() settings.OpenVPN, getPortForwarded func() uint16) Server {
return &server{
address: address,
logger: logger.WithPrefix("http server: "),
restartOpenvpn: restartOpenvpn,
restartUnbound: restartUnbound,
getOpenvpnSettings: getOpenvpnSettings,
getPortForwarded: getPortForwarded,
}
}
@@ -61,6 +67,10 @@ func (s *server) makeHandler() http.HandlerFunc {
s.restartOpenvpn()
case "/unbound/actions/restart":
s.restartUnbound()
case "/openvpn/portforwarded":
s.handleGetPortForwarded(w)
case "/openvpn/settings":
s.handleGetOpenvpnSettings(w)
default:
routeDoesNotExist(s.logger, w, r)
}

View File

@@ -1,6 +1,7 @@
package settings
import (
"fmt"
"net"
"strings"
@@ -10,6 +11,7 @@ import (
// Firewall contains settings to customize the firewall operation
type Firewall struct {
AllowedSubnets []net.IPNet
VPNInputPorts []uint16
Enabled bool
Debug bool
}
@@ -22,9 +24,15 @@ func (f *Firewall) String() string {
if !f.Enabled {
return "Firewall settings: disabled"
}
vpnInputPorts := make([]string, len(f.VPNInputPorts))
for i, port := range f.VPNInputPorts {
vpnInputPorts[i] = fmt.Sprintf("%d", port)
}
settingsList := []string{
"Firewall settings:",
"Allowed subnets: " + strings.Join(allowedSubnets, ", "),
"VPN input ports: " + strings.Join(vpnInputPorts, ", "),
}
if f.Debug {
settingsList = append(settingsList, "Debug: on")
@@ -38,6 +46,10 @@ func GetFirewallSettings(paramsReader params.Reader) (settings Firewall, err err
if err != nil {
return settings, err
}
settings.VPNInputPorts, err = paramsReader.GetVPNInputPorts()
if err != nil {
return settings, err
}
settings.Enabled, err = paramsReader.GetFirewall()
if err != nil {
return settings, err

View File

@@ -11,13 +11,13 @@ import (
// OpenVPN contains settings to configure the OpenVPN client
type OpenVPN struct {
User string
Password string
Verbosity int
Root bool
Cipher string
Auth string
Provider models.ProviderSettings
User string `json:"user"`
Password string `json:"-"`
Verbosity int `json:"verbosity"`
Root bool `json:"runAsRoot"`
Cipher string `json:"cipher"`
Auth string `json:"auth"`
Provider models.ProviderSettings `json:"provider"`
}
// GetOpenVPNSettings obtains the OpenVPN settings using the params functions
@@ -66,6 +66,8 @@ func GetOpenVPNSettings(paramsReader params.Reader, vpnProvider models.VPNProvid
settings.Provider, err = GetVyprvpnSettings(paramsReader)
case constants.Nordvpn:
settings.Provider, err = GetNordvpnSettings(paramsReader)
case constants.Purevpn:
settings.Provider, err = GetPurevpnSettings(paramsReader)
default:
err = fmt.Errorf("VPN service provider %q is not valid", vpnProvider)
}

View File

@@ -0,0 +1,27 @@
package settings
import (
"encoding/json"
"testing"
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_OpenVPN_JSON(t *testing.T) {
t.Parallel()
in := OpenVPN{
Root: true,
Provider: models.ProviderSettings{
Name: "name",
},
}
data, err := json.Marshal(in)
require.NoError(t, err)
assert.Equal(t, `{"user":"","verbosity":0,"runAsRoot":true,"cipher":"","auth":"","provider":{"name":"name","serverSelection":{"networkProtocol":"","region":"","group":"","country":"","city":"","isp":"","owned":false,"customPort":0,"number":0,"encryptionPreset":""},"extraConfig":{"encryptionPreset":""},"portForwarding":{"enabled":false,"filepath":""}}}`, string(data))
var out OpenVPN
err = json.Unmarshal(data, &out)
require.NoError(t, err)
assert.Equal(t, in, out)
}

View File

@@ -175,3 +175,29 @@ func GetNordvpnSettings(paramsReader params.Reader) (settings models.ProviderSet
}
return settings, nil
}
// GetPurevpnSettings obtains Purevpn settings from environment variables using the params package.
func GetPurevpnSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
settings.Name = constants.Mullvad
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
if err != nil {
return settings, err
}
settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP()
if err != nil {
return settings, err
}
settings.ServerSelection.Region, err = paramsReader.GetPurevpnRegion()
if err != nil {
return settings, err
}
settings.ServerSelection.Country, err = paramsReader.GetPurevpnCountry()
if err != nil {
return settings, err
}
settings.ServerSelection.City, err = paramsReader.GetPurevpnCity()
if err != nil {
return settings, err
}
return settings, nil
}

View File

@@ -30,6 +30,7 @@ type looper struct {
streamMerger command.StreamMerger
uid int
gid int
defaultInterface string
restart chan struct{}
start chan struct{}
stop chan struct{}
@@ -44,7 +45,7 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
}
func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings settings.ShadowSocks, dnsSettings settings.DNS,
logger logging.Logger, streamMerger command.StreamMerger, uid, gid int) Looper {
logger logging.Logger, streamMerger command.StreamMerger, uid, gid int, defaultInterface string) Looper {
return &looper{
conf: conf,
firewallConf: firewallConf,
@@ -54,6 +55,7 @@ func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings s
streamMerger: streamMerger,
uid: uid,
gid: gid,
defaultInterface: defaultInterface,
restart: make(chan struct{}),
start: make(chan struct{}),
stop: make(chan struct{}),
@@ -141,7 +143,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
continue
}
}
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port); err != nil {
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port, l.defaultInterface); err != nil {
l.logger.Error(err)
continue
}

View File

@@ -29,6 +29,7 @@ type looper struct {
streamMerger command.StreamMerger
uid int
gid int
defaultInterface string
restart chan struct{}
start chan struct{}
stop chan struct{}
@@ -43,7 +44,7 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
}
func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings settings.TinyProxy,
logger logging.Logger, streamMerger command.StreamMerger, uid, gid int) Looper {
logger logging.Logger, streamMerger command.StreamMerger, uid, gid int, defaultInterface string) Looper {
return &looper{
conf: conf,
firewallConf: firewallConf,
@@ -52,6 +53,7 @@ func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings s
streamMerger: streamMerger,
uid: uid,
gid: gid,
defaultInterface: defaultInterface,
restart: make(chan struct{}),
start: make(chan struct{}),
stop: make(chan struct{}),
@@ -133,7 +135,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
continue
}
}
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port); err != nil {
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port, l.defaultInterface); err != nil {
l.logger.Error(err)
continue
}