Compare commits

...

9 Commits

Author SHA1 Message Date
Quentin McGaw
0d2ca377df PIA port forwarding final fixes (#259)
- Returns an error if the server does not support port forwarding
- TLS verification using the server common name obtained through the API
- Updated readme
- Fixes #236
2020-10-15 22:53:13 -04:00
Quentin McGaw
98f778c3bb Improve timing behavior of ticking in loops 2020-10-15 23:20:36 +00:00
Quentin McGaw
9b9ae69404 Repurpose OPENVPN_TARGET_IP for #229 2020-10-12 20:21:26 +00:00
Quentin McGaw
1c747a10c8 Fix CN data for PIA v4 servers 2020-10-12 19:34:36 +00:00
Quentin McGaw
c4354871f7 Single connection written to openvpn configuration (#258)
- From now only a single OpenVPN connection is written to the OpenVPN configuration file
- If multiple connections are matched given the user parameters (i.e. city, region), it is picked at pseudo random using the current time as the pseudo random seed.
- Not relying on Openvpn picking a random remote address, may refer to #229 
- Program is aware of which connection is to be used, in order to use its matching CN for port forwarding TLS verification with PIA v4 servers, see #236 
- Simplified firewall mechanisms
2020-10-12 15:29:58 -04:00
Quentin McGaw
9f6450502c Obtain PIA v4 server information from API (#257)
- Obtain CN for port forwarding https verification
- Obtain for each server if they support port forwarding
- Obtain for each server their IP address for openvpn UDP and openvpn TCP (one for each)
- Updater program updated to use API
- Hardcoded values updated for PIA v3 and v4 servers
- Clearer separation between pia v3 and v4
- Fixes #250
2020-10-12 13:57:45 -04:00
Quentin McGaw
ae7fc5fe96 Fix guard pattern for max parallel DNS requests 2020-10-12 17:35:46 +00:00
Quentin McGaw
ec157f102b PIA nextgen portforward (#242)
* Split provider/pia.go in piav3.go and piav4.go
* Change port forwarding signature
* Enable port forwarding parameter for PIA v4
* Fix VPN gateway IP obtention
* Setup HTTP client for TLS with custom cert
* Error message for regions not supporting pf
2020-10-12 10:55:08 -04:00
Quentin McGaw
fbecbc1c82 Fix updater guard pattern (#255) 2020-10-01 17:56:14 -04:00
45 changed files with 1517 additions and 817 deletions

View File

@@ -113,4 +113,6 @@ RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables i
deluser tinyproxy && \ deluser tinyproxy && \
deluser unbound && \ deluser unbound && \
mkdir /gluetun mkdir /gluetun
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
ENV GODEBUG=x509ignoreCN=0
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint COPY --from=builder /tmp/gobuild/entrypoint /entrypoint

View File

@@ -100,7 +100,7 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use | | `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level | | `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
| `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root | | `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root |
| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN server (or gateway) IP address to use | | `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN IP address to use |
| `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_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 | | `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
| `OPENVPN_IPV6` | `off` | `on`, `off` | Enable tunneling of IPv6 (only for Mullvad) | | `OPENVPN_IPV6` | `off` | `on`, `off` | Enable tunneling of IPv6 (only for Mullvad) |
@@ -115,8 +115,8 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
| 🏁 `PASSWORD` | | | Your password | | 🏁 `PASSWORD` | | | Your password |
| `REGION` | | 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 | | `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server **for old only** | | `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server |
| `PORT_FORWARDING_STATUS_FILE` | `/tmp/gluetun/forwarded_port` | Any filepath | Filepath to store the forwarded port number **for old only** | | `PORT_FORWARDING_STATUS_FILE` | `/tmp/gluetun/forwarded_port` | Any filepath | Filepath to store the forwarded port number |
- Mullvad - Mullvad
@@ -352,11 +352,11 @@ There are various ways to achieve this, depending on your use case.
## Private Internet Access port forwarding ## Private Internet Access port forwarding
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=/tmp/gluetun/forwarded_port`.
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. It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
For `VPNSP=private internet access` (default), you will keep the same forwarded port for 60 days as long as you bind mount the `/gluetun` directory.
You can also use the HTTP control server (see below) to get the port forwarded. You can also use the HTTP control server (see below) to get the port forwarded.
## HTTP control server ## HTTP control server

View File

@@ -3,9 +3,11 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@@ -188,7 +190,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady) go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid, allServers, openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid, allServers,
ovpnConf, firewallConf, logger, client, fileManager, streamMerger, cancel) ovpnConf, firewallConf, routingConf, logger, httpClient, fileManager, streamMerger, cancel)
wg.Add(1) wg.Add(1)
// wait for restartOpenvpn // wait for restartOpenvpn
go openvpnLooper.Run(ctx, wg) go openvpnLooper.Run(ctx, wg)
@@ -327,9 +329,9 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
logger.Error(line) logger.Error(line)
} }
switch { switch {
case line == "openvpn: Initialization Sequence Completed": case strings.Contains(line, "Initialization Sequence Completed"):
signalTunnelReady() signalTunnelReady()
case line == "openvpn: TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)": case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"):
logger.Warn("This means that either...") logger.Warn("This means that either...")
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information") logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information")
logger.Warn("2. The VPN server crashed, try changing region") logger.Warn("2. The VPN server crashed, try changing region")
@@ -341,10 +343,11 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
}) })
} }
//nolint:gocognit
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dnsReadyCh <-chan struct{}, func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dnsReadyCh <-chan struct{},
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper, unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
routing routing.Routing, logger logging.Logger, httpClient *http.Client, routing routing.Routing, logger logging.Logger, httpClient *http.Client,
versionInformation, portForwardingEnabled bool, startPortForward func()) { versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
defer wg.Done() defer wg.Done()
tickerWg := &sync.WaitGroup{} tickerWg := &sync.WaitGroup{}
// for linters only // for linters only
@@ -364,18 +367,35 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
tickerWg.Add(2) tickerWg.Add(2)
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg) go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg) go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
if portForwardingEnabled {
time.AfterFunc(5*time.Second, startPortForward)
}
defaultInterface, _, err := routing.DefaultRoute() defaultInterface, _, err := routing.DefaultRoute()
if err != nil { if err != nil {
logger.Warn(err) logger.Warn(err)
} else { } else {
vpnGatewayIP, err := routing.VPNGatewayIP(defaultInterface) vpnDestination, err := routing.VPNDestinationIP(defaultInterface)
if err != nil { if err != nil {
logger.Warn(err) logger.Warn(err)
} else { } else {
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP) logger.Info("VPN routing IP address: %s", vpnDestination)
}
}
if portForwardingEnabled {
// TODO make instantaneous once v3 go out of service
const waitDuration = 5 * time.Second
timer := time.NewTimer(waitDuration)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
continue
case <-timer.C:
// vpnGateway required only for PIA v4
vpnGateway, err := routing.VPNLocalGatewayIP()
if err != nil {
logger.Error(err)
}
logger.Info("VPN gateway IP address: %s", vpnGateway)
startPortForward(vpnGateway)
} }
} }
case <-dnsReadyCh: case <-dnsReadyCh:

View File

@@ -70,13 +70,13 @@ func OpenvpnConfig() error {
if err != nil { if err != nil {
return err return err
} }
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers) providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
connections, err := providerConf.GetOpenVPNConnections(allSettings.OpenVPN.Provider.ServerSelection) connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
if err != nil { if err != nil {
return err return err
} }
lines := providerConf.BuildConf( lines := providerConf.BuildConf(
connections, connection,
allSettings.OpenVPN.Verbosity, allSettings.OpenVPN.Verbosity,
allSettings.System.UID, allSettings.System.UID,
allSettings.System.GID, allSettings.System.GID,

View File

@@ -15,6 +15,8 @@ const (
OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf" OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf"
// OpenVPNConf is the file path to the OpenVPN client configuration file // OpenVPNConf is the file path to the OpenVPN client configuration file
OpenVPNConf models.Filepath = "/etc/openvpn/target.ovpn" OpenVPNConf models.Filepath = "/etc/openvpn/target.ovpn"
// PIAPortForward is the file path to the port forwarding JSON information for PIA v4 servers
PIAPortForward models.Filepath = "/gluetun/piaportforward.json"
// TunnelDevice is the file path to tun device // TunnelDevice is the file path to tun device
TunnelDevice models.Filepath = "/dev/net/tun" TunnelDevice models.Filepath = "/dev/net/tun"
// NetRoute is the path to the file containing information on the network route // NetRoute is the path to the file containing information on the network route

View File

@@ -26,90 +26,101 @@ func PIAGeoChoices() (choices []string) {
func PIAServers() []models.PIAServer { func PIAServers() []models.PIAServer {
return []models.PIAServer{ return []models.PIAServer{
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 74, 184}}}, {Region: "AU Melbourne", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "melbourne405", IPs: []net.IP{{103, 2, 198, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "melbourne405", IPs: []net.IP{{103, 2, 198, 103}}}},
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 170}}}, {Region: "AU Perth", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 186}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 188}}}},
{Region: "AU Sydney", IPs: []net.IP{{103, 2, 196, 167}}}, {Region: "AU Sydney", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sydney405", IPs: []net.IP{{27, 50, 76, 132}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney405", IPs: []net.IP{{27, 50, 76, 132}}}},
{Region: "Algeria", IPs: []net.IP{{45, 133, 91, 210}}}, {Region: "Albania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 131}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 137}}}},
{Region: "Andorra", IPs: []net.IP{{45, 139, 49, 241}}}, {Region: "Algeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "algiers402", IPs: []net.IP{{45, 133, 91, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers402", IPs: []net.IP{{45, 133, 91, 227}}}},
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 82}}}, {Region: "Andorra", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "andorra401", IPs: []net.IP{{45, 139, 49, 232}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra401", IPs: []net.IP{{45, 139, 49, 238}}}},
{Region: "Armenia", IPs: []net.IP{{45, 139, 50, 232}}}, {Region: "Argentina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 92}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 89}}}},
{Region: "Austria", IPs: []net.IP{{156, 146, 60, 14}}}, {Region: "Armenia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "armenia402", IPs: []net.IP{{45, 139, 50, 229}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia402", IPs: []net.IP{{45, 139, 50, 213}}}},
{Region: "Bahamas", IPs: []net.IP{{45, 132, 143, 206}}}, {Region: "Austria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 104}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 100}}}},
{Region: "Bangladesh", IPs: []net.IP{{45, 132, 142, 210}}}, {Region: "Bahamas", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 206}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 229}}}},
{Region: "Belgium", IPs: []net.IP{{5, 253, 205, 147}}}, {Region: "Belgium", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "brussels403", IPs: []net.IP{{5, 253, 205, 147}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "brussels403", IPs: []net.IP{{5, 253, 205, 153}}}},
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 130}}}, {Region: "Bosnia and Herzegovina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sarajevo401", IPs: []net.IP{{185, 212, 111, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sarajevo401", IPs: []net.IP{{185, 212, 111, 77}}}},
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 13}}}, {Region: "Brazil", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saopaolo402", IPs: []net.IP{{188, 241, 177, 56}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saopaolo402", IPs: []net.IP{{188, 241, 177, 51}}}},
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 81}}}, {Region: "Bulgaria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sofia401", IPs: []net.IP{{217, 138, 221, 131}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sofia401", IPs: []net.IP{{217, 138, 221, 133}}}},
{Region: "Cambodia", IPs: []net.IP{{188, 215, 235, 103}}}, {Region: "CA Montreal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montreal403", IPs: []net.IP{{172, 98, 71, 62}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montreal403", IPs: []net.IP{{172, 98, 71, 59}}}},
{Region: "China", IPs: []net.IP{{45, 132, 193, 234}}}, {Region: "CA Ontario", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "ontario402", IPs: []net.IP{{172, 83, 47, 138}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "ontario402", IPs: []net.IP{{172, 83, 47, 196}}}},
{Region: "Cyprus", IPs: []net.IP{{45, 132, 137, 235}}}, {Region: "CA Toronto", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 250}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 251}}}},
{Region: "Czech Republic", IPs: []net.IP{{212, 102, 39, 194}}}, {Region: "CA Vancouver", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 70}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 18}}}},
{Region: "DE Berlin", IPs: []net.IP{{89, 36, 76, 69}}}, {Region: "Cambodia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 105}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 102}}}},
{Region: "DE Frankfurt", IPs: []net.IP{{185, 216, 33, 164}}}, {Region: "China", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 212}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 216}}}},
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 124}}}, {Region: "Cyprus", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cyprus402", IPs: []net.IP{{45, 132, 137, 220}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cyprus402", IPs: []net.IP{{45, 132, 137, 225}}}},
{Region: "Egypt", IPs: []net.IP{{188, 214, 122, 119}}}, {Region: "Czech Republic", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 148}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 149}}}},
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 10}}}, {Region: "DE Berlin", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "berlin410", IPs: []net.IP{{89, 36, 76, 153}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "berlin410", IPs: []net.IP{{89, 36, 76, 149}}}},
{Region: "France", IPs: []net.IP{{156, 146, 63, 210}}}, {Region: "DE Frankfurt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 96}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 106}}}},
{Region: "Georgia", IPs: []net.IP{{45, 132, 138, 236}}}, {Region: "Denmark", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 93}}}},
{Region: "Greenland", IPs: []net.IP{{45, 131, 209, 233}}}, {Region: "Egypt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cairo401", IPs: []net.IP{{188, 214, 122, 106}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cairo401", IPs: []net.IP{{188, 214, 122, 104}}}},
{Region: "Hungary", IPs: []net.IP{{217, 138, 192, 222}}}, {Region: "Estonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}},
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 85}}}, {Region: "Finland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 45}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 45}}}},
{Region: "India", IPs: []net.IP{{103, 26, 205, 251}}}, {Region: "France", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "paris402", IPs: []net.IP{{156, 146, 63, 159}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "paris402", IPs: []net.IP{{156, 146, 63, 159}}}},
{Region: "Iran", IPs: []net.IP{{45, 131, 4, 208}}}, {Region: "Georgia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "georgia401", IPs: []net.IP{{45, 132, 138, 245}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "georgia401", IPs: []net.IP{{45, 132, 138, 236}}}},
{Region: "Ireland", IPs: []net.IP{{5, 157, 13, 41}}}, {Region: "Greece", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 80}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 84}}}},
{Region: "Isle of Man", IPs: []net.IP{{45, 132, 140, 213}}}, {Region: "Greenland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 222}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 208}}}},
{Region: "Israel", IPs: []net.IP{{185, 77, 248, 10}}}, {Region: "Hong Kong", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "hongkong402", IPs: []net.IP{{86, 107, 104, 234}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "hongkong402", IPs: []net.IP{{86, 107, 104, 240}}}},
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 77}}}, {Region: "Hungary", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 121}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 125}}}},
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 164}}}, {Region: "Iceland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}},
{Region: "Kazakhstan", IPs: []net.IP{{45, 133, 88, 231}}}, {Region: "India", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mumbai405", IPs: []net.IP{{45, 120, 139, 97}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mumbai405", IPs: []net.IP{{45, 120, 139, 97}}}},
{Region: "Liechtenstein", IPs: []net.IP{{45, 139, 48, 236}}}, {Region: "Iran", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "iran402", IPs: []net.IP{{45, 131, 4, 219}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "iran402", IPs: []net.IP{{45, 131, 4, 218}}}},
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 80}}}, {Region: "Ireland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dublin404", IPs: []net.IP{{193, 56, 252, 28}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dublin404", IPs: []net.IP{{193, 56, 252, 24}}}},
{Region: "Macao", IPs: []net.IP{{45, 137, 197, 207}}}, {Region: "Isle of Man", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "douglas401", IPs: []net.IP{{45, 132, 140, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "douglas401", IPs: []net.IP{{45, 132, 140, 244}}}},
{Region: "Malta", IPs: []net.IP{{45, 137, 198, 235}}}, {Region: "Israel", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "jerusalem401", IPs: []net.IP{{185, 77, 248, 19}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "jerusalem401", IPs: []net.IP{{185, 77, 248, 17}}}},
{Region: "Mexico", IPs: []net.IP{{77, 81, 142, 5}}}, {Region: "Italy", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "milano402", IPs: []net.IP{{156, 146, 41, 20}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "milano402", IPs: []net.IP{{156, 146, 41, 42}}}},
{Region: "Moldova", IPs: []net.IP{{178, 175, 129, 40}}}, {Region: "Japan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 135}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 157}}}},
{Region: "Monaco", IPs: []net.IP{{45, 137, 199, 237}}}, {Region: "Kazakhstan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 229}}}},
{Region: "Mongolia", IPs: []net.IP{{45, 139, 51, 211}}}, {Region: "Latvia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 12}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 12}}}},
{Region: "Montenegro", IPs: []net.IP{{45, 131, 208, 206}}}, {Region: "Liechtenstein", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "liechtenstein401", IPs: []net.IP{{45, 139, 48, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "liechtenstein401", IPs: []net.IP{{45, 139, 48, 242}}}},
{Region: "Morocco", IPs: []net.IP{{45, 131, 211, 234}}}, {Region: "Lithuania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 163}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 163}}}},
{Region: "Netherlands", IPs: []net.IP{{37, 235, 101, 73}}}, {Region: "Luxembourg", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "luxembourg401", IPs: []net.IP{{92, 223, 89, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "luxembourg401", IPs: []net.IP{{92, 223, 89, 78}}}},
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 70}}}, {Region: "Macedonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 115}}}},
{Region: "Nigeria", IPs: []net.IP{{45, 137, 196, 208}}}, {Region: "Malta", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "malta401", IPs: []net.IP{{45, 137, 198, 238}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "malta401", IPs: []net.IP{{45, 137, 198, 244}}}},
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 82}}}, {Region: "Mexico", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 7}}}},
{Region: "Panama", IPs: []net.IP{{45, 131, 210, 206}}}, {Region: "Moldova", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 43}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 44}}}},
{Region: "Philippines", IPs: []net.IP{{188, 214, 125, 138}}}, {Region: "Monaco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 226}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 218}}}},
{Region: "Poland", IPs: []net.IP{{217, 138, 209, 243}}}, {Region: "Montenegro", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montenegro402", IPs: []net.IP{{45, 131, 208, 212}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montenegro402", IPs: []net.IP{{45, 131, 208, 212}}}},
{Region: "Qatar", IPs: []net.IP{{45, 131, 7, 209}}}, {Region: "Morocco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 233}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 248}}}},
{Region: "Romania", IPs: []net.IP{{185, 45, 15, 22}}}, {Region: "Netherlands", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "amsterdam416", IPs: []net.IP{{212, 102, 35, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "amsterdam416", IPs: []net.IP{{212, 102, 35, 136}}}},
{Region: "Saudi Arabia", IPs: []net.IP{{45, 131, 6, 208}}}, {Region: "New Zealand", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 89}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 94}}}},
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 248}}}, {Region: "Norway", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "oslo403", IPs: []net.IP{{46, 246, 122, 124}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "oslo403", IPs: []net.IP{{46, 246, 122, 99}}}},
{Region: "Singapore", IPs: []net.IP{{156, 146, 57, 123}}}, {Region: "Panama", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "panama401", IPs: []net.IP{{45, 131, 210, 248}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "panama401", IPs: []net.IP{{45, 131, 210, 231}}}},
{Region: "South Africa", IPs: []net.IP{{154, 16, 93, 35}}}, {Region: "Philippines", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 142}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 142}}}},
{Region: "Spain", IPs: []net.IP{{195, 181, 167, 42}}}, {Region: "Poland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "warsaw402", IPs: []net.IP{{194, 110, 114, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "warsaw402", IPs: []net.IP{{194, 110, 114, 13}}}},
{Region: "Sri Lanka", IPs: []net.IP{{45, 132, 136, 232}}}, {Region: "Portugal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lisbon401", IPs: []net.IP{{89, 26, 241, 72}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lisbon401", IPs: []net.IP{{89, 26, 241, 76}}}},
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 150}}}, {Region: "Qatar", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "qatar401", IPs: []net.IP{{45, 131, 7, 234}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "qatar401", IPs: []net.IP{{45, 131, 7, 232}}}},
{Region: "Switzerland", IPs: []net.IP{{212, 102, 37, 77}}}, {Region: "Romania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 92}}}},
{Region: "Taiwan", IPs: []net.IP{{188, 214, 106, 70}}}, {Region: "Saudi Arabia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 238}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 231}}}},
{Region: "Turkey", IPs: []net.IP{{188, 213, 34, 87}}}, {Region: "Serbia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}},
{Region: "UK London", IPs: []net.IP{{37, 235, 96, 26}}}, {Region: "Singapore", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 210}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 190}}}},
{Region: "UK Manchester", IPs: []net.IP{{193, 239, 84, 60}}}, {Region: "Slovakia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bratislava401", IPs: []net.IP{{37, 120, 221, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bratislava401", IPs: []net.IP{{37, 120, 221, 83}}}},
{Region: "US Atlanta", IPs: []net.IP{{195, 181, 171, 76}}}, {Region: "South Africa", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 46}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 44}}}},
{Region: "US California", IPs: []net.IP{{37, 235, 108, 19}}}, {Region: "Spain", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 33}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 29}}}},
{Region: "US Chicago", IPs: []net.IP{{154, 21, 28, 111}}}, {Region: "Sri Lanka", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 224}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 216}}}},
{Region: "US Denver", IPs: []net.IP{{70, 39, 126, 143}}}, {Region: "Sweden", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "stockholm404", IPs: []net.IP{{195, 246, 120, 140}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "stockholm404", IPs: []net.IP{{195, 246, 120, 116}}}},
{Region: "US Florida", IPs: []net.IP{{37, 235, 98, 18}}}, {Region: "Switzerland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 104}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 84}}}},
{Region: "US Houston", IPs: []net.IP{{74, 81, 92, 147}}}, {Region: "Taiwan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 71}}}},
{Region: "US New Jersey", IPs: []net.IP{{37, 235, 103, 75}}}, {Region: "Turkey", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 71}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 76}}}},
{Region: "US New York", IPs: []net.IP{{156, 146, 55, 213}}}, {Region: "UK London", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london412", IPs: []net.IP{{37, 235, 96, 109}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london412", IPs: []net.IP{{37, 235, 96, 109}}}},
{Region: "US Seattle", IPs: []net.IP{{156, 146, 48, 14}}}, {Region: "UK Manchester", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "manchester460", IPs: []net.IP{{37, 120, 159, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "manchester460", IPs: []net.IP{{37, 120, 159, 122}}}},
{Region: "US Silicon Valley", IPs: []net.IP{{154, 21, 212, 228}}}, {Region: "UK Southampton", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 223}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 189}}}},
{Region: "US Texas", IPs: []net.IP{{154, 29, 131, 17}}}, {Region: "US Atlanta", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "atlanta421", IPs: []net.IP{{154, 21, 21, 77}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "atlanta421", IPs: []net.IP{{154, 21, 21, 70}}}},
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 5, 172}}}, {Region: "US California", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{37, 235, 107, 62}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{37, 235, 107, 17}}}},
{Region: "US West", IPs: []net.IP{{193, 37, 254, 239}}}, {Region: "US Chicago", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chicago416", IPs: []net.IP{{154, 21, 114, 12}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chicago416", IPs: []net.IP{{154, 21, 114, 12}}}},
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 51}}}, {Region: "US Denver", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "denver402", IPs: []net.IP{{70, 39, 126, 157}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "denver402", IPs: []net.IP{{70, 39, 126, 175}}}},
{Region: "United Arab Emirates", IPs: []net.IP{{45, 131, 5, 233}}}, {Region: "US East", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newjersey402", IPs: []net.IP{{37, 235, 103, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newjersey402", IPs: []net.IP{{37, 235, 103, 131}}}},
{Region: "Venezuela", IPs: []net.IP{{45, 133, 89, 212}}}, {Region: "US Florida", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "miami405", IPs: []net.IP{{37, 235, 98, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "miami405", IPs: []net.IP{{37, 235, 98, 188}}}},
{Region: "Vietnam", IPs: []net.IP{{188, 214, 152, 67}}}, {Region: "US Houston", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "houston418", IPs: []net.IP{{205, 251, 154, 205}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "houston418", IPs: []net.IP{{205, 251, 154, 208}}}},
{Region: "US Las Vegas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lasvegas402", IPs: []net.IP{{45, 89, 173, 178}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lasvegas402", IPs: []net.IP{{45, 89, 173, 181}}}},
{Region: "US New York", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newyork403", IPs: []net.IP{{156, 146, 54, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newyork403", IPs: []net.IP{{156, 146, 54, 63}}}},
{Region: "US Seattle", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "seattle417", IPs: []net.IP{{154, 21, 20, 187}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "seattle417", IPs: []net.IP{{154, 21, 20, 169}}}},
{Region: "US Silicon Valley", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "siliconvalley401", IPs: []net.IP{{154, 21, 212, 40}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "siliconvalley401", IPs: []net.IP{{154, 21, 212, 14}}}},
{Region: "US Texas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dallas401", IPs: []net.IP{{156, 146, 53, 180}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dallas401", IPs: []net.IP{{156, 146, 53, 186}}}},
{Region: "US Washington DC", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "washington412", IPs: []net.IP{{23, 105, 168, 143}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "washington412", IPs: []net.IP{{23, 105, 168, 150}}}},
{Region: "US West", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 67}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 121}}}},
{Region: "Ukraine", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kiev402", IPs: []net.IP{{62, 149, 20, 23}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kiev402", IPs: []net.IP{{62, 149, 20, 22}}}},
{Region: "United Arab Emirates", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 146}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 148}}}},
{Region: "Venezuela", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "venezuela402", IPs: []net.IP{{45, 133, 89, 217}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "venezuela402", IPs: []net.IP{{45, 133, 89, 217}}}},
{Region: "Vietnam", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 70}}}},
} }
} }
@@ -122,39 +133,39 @@ func PIAOldGeoChoices() (choices []string) {
return choices return choices
} }
func PIAOldServers() []models.PIAServer { func PIAOldServers() []models.PIAOldServer {
return []models.PIAServer{ return []models.PIAOldServer{
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {27, 50, 82, 133}, {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, 119}, {43, 250, 204, 123}, {43, 250, 204, 125}}}, {Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {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, 119}, {43, 250, 204, 123}, {43, 250, 204, 125}}},
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 91}, {43, 250, 205, 93}, {43, 250, 205, 95}}}, {Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 91}, {43, 250, 205, 93}, {43, 250, 205, 95}}},
{Region: "AU Sydney", IPs: []net.IP{{27, 50, 68, 23}, {27, 50, 70, 87}, {27, 50, 77, 251}, {27, 50, 81, 117}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 43}, {118, 127, 60, 51}, {221, 121, 145, 137}, {221, 121, 145, 145}, {221, 121, 145, 147}, {221, 121, 145, 159}, {221, 121, 146, 203}, {221, 121, 148, 221}, {221, 121, 152, 215}}}, {Region: "AU Sydney", IPs: []net.IP{{27, 50, 68, 23}, {27, 50, 70, 87}, {27, 50, 77, 251}, {27, 50, 81, 117}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 51}, {221, 121, 145, 135}, {221, 121, 145, 137}, {221, 121, 145, 145}, {221, 121, 145, 147}, {221, 121, 145, 159}, {221, 121, 146, 203}, {221, 121, 148, 221}, {221, 121, 152, 215}}},
{Region: "Albania", IPs: []net.IP{{31, 171, 154, 114}}}, {Region: "Albania", IPs: []net.IP{{31, 171, 154, 114}}},
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 100}}}, {Region: "Argentina", IPs: []net.IP{{190, 106, 134, 100}}},
{Region: "Austria", IPs: []net.IP{{89, 187, 168, 6}, {156, 146, 60, 129}}}, {Region: "Austria", IPs: []net.IP{{89, 187, 168, 6}, {156, 146, 60, 129}}},
{Region: "Belgium", IPs: []net.IP{{77, 243, 191, 18}, {77, 243, 191, 19}, {77, 243, 191, 20}, {77, 243, 191, 21}, {77, 243, 191, 22}, {77, 243, 191, 23}, {185, 104, 186, 26}, {185, 232, 21, 26}, {185, 232, 21, 27}, {185, 232, 21, 28}, {185, 232, 21, 29}}}, {Region: "Belgium", IPs: []net.IP{{77, 243, 191, 18}, {77, 243, 191, 19}, {77, 243, 191, 20}, {185, 232, 21, 26}}},
{Region: "Bosnia and Herzegovina", IPs: []net.IP{{185, 164, 35, 54}}}, {Region: "Bosnia and Herzegovina", IPs: []net.IP{{185, 164, 35, 54}}},
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 66}}}, {Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 66}}},
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 194}, {199, 36, 223, 130}, {199, 36, 223, 194}}}, {Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 194}, {199, 36, 223, 130}, {199, 36, 223, 194}}},
{Region: "CA Ontario", IPs: []net.IP{{162, 219, 176, 42}, {162, 219, 176, 130}, {162, 219, 176, 194}, {184, 75, 208, 2}, {184, 75, 208, 18}, {184, 75, 208, 34}, {184, 75, 208, 66}, {184, 75, 208, 90}, {184, 75, 208, 114}, {184, 75, 208, 122}, {184, 75, 208, 130}, {184, 75, 208, 146}, {184, 75, 208, 170}, {184, 75, 210, 18}, {184, 75, 210, 194}, {184, 75, 214, 18}, {184, 75, 215, 18}, {184, 75, 215, 26}, {184, 75, 215, 66}, {184, 75, 215, 74}}}, {Region: "CA Ontario", IPs: []net.IP{{162, 219, 176, 26}, {162, 219, 176, 42}, {184, 75, 208, 2}, {184, 75, 208, 90}, {184, 75, 208, 114}, {184, 75, 208, 122}, {184, 75, 208, 130}, {184, 75, 208, 146}, {184, 75, 208, 170}, {184, 75, 208, 202}, {184, 75, 210, 18}, {184, 75, 210, 98}, {184, 75, 210, 106}, {184, 75, 213, 186}, {184, 75, 213, 218}, {184, 75, 214, 18}, {184, 75, 215, 18}, {184, 75, 215, 26}, {184, 75, 215, 66}, {184, 75, 215, 74}}},
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 130}, {172, 98, 92, 66}, {172, 98, 92, 130}}}, {Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 130}, {66, 115, 145, 199}, {172, 98, 92, 66}, {172, 98, 92, 130}, {172, 98, 92, 194}}},
{Region: "CA Vancouver", IPs: []net.IP{{162, 216, 47, 66}, {162, 216, 47, 194}, {172, 98, 89, 130}, {172, 98, 89, 194}}}, {Region: "CA Vancouver", IPs: []net.IP{{162, 216, 47, 66}, {162, 216, 47, 194}, {172, 98, 89, 130}, {172, 98, 89, 194}}},
{Region: "Czech Republic", IPs: []net.IP{{185, 216, 35, 66}, {212, 102, 39, 1}}}, {Region: "Czech Republic", IPs: []net.IP{{212, 102, 39, 1}}},
{Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 230}, {185, 230, 127, 231}, {185, 230, 127, 235}, {185, 230, 127, 236}, {185, 230, 127, 237}, {185, 230, 127, 238}, {185, 230, 127, 239}, {185, 230, 127, 241}, {193, 176, 86, 122}, {193, 176, 86, 124}, {193, 176, 86, 130}, {193, 176, 86, 134}, {193, 176, 86, 142}, {193, 176, 86, 150}, {193, 176, 86, 154}, {193, 176, 86, 166}, {193, 176, 86, 170}, {193, 176, 86, 174}, {193, 176, 86, 178}, {194, 36, 108, 6}}}, {Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 238}, {193, 176, 86, 122}, {193, 176, 86, 123}, {193, 176, 86, 134}, {193, 176, 86, 178}, {194, 36, 108, 6}}},
{Region: "DE Frankfurt", IPs: []net.IP{{195, 181, 170, 225}, {195, 181, 170, 239}, {195, 181, 170, 240}, {195, 181, 170, 241}, {195, 181, 170, 242}, {212, 102, 57, 138}}}, {Region: "DE Frankfurt", IPs: []net.IP{{195, 181, 170, 239}, {195, 181, 170, 240}, {195, 181, 170, 241}, {195, 181, 170, 242}, {195, 181, 170, 243}, {195, 181, 170, 244}, {212, 102, 57, 138}}},
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 34}}}, {Region: "Denmark", IPs: []net.IP{{188, 126, 94, 34}}},
{Region: "Estonia", IPs: []net.IP{{77, 247, 111, 98}}}, {Region: "Estonia", IPs: []net.IP{{77, 247, 111, 82}, {77, 247, 111, 98}, {77, 247, 111, 114}, {77, 247, 111, 130}}},
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 194}}}, {Region: "Finland", IPs: []net.IP{{188, 126, 89, 4}, {188, 126, 89, 194}}},
{Region: "France", IPs: []net.IP{{156, 146, 63, 1}, {156, 146, 63, 65}}}, {Region: "France", IPs: []net.IP{{156, 146, 63, 1}, {156, 146, 63, 65}}},
{Region: "Greece", IPs: []net.IP{{154, 57, 3, 91}, {154, 57, 3, 106}, {154, 57, 3, 145}}}, {Region: "Greece", IPs: []net.IP{{154, 57, 3, 91}, {154, 57, 3, 106}, {154, 57, 3, 145}}},
{Region: "Hungary", IPs: []net.IP{{185, 128, 26, 24}}}, {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: "Iceland", IPs: []net.IP{{45, 133, 193, 50}, {45, 133, 193, 66}}}, {Region: "Iceland", IPs: []net.IP{{45, 133, 193, 50}}},
{Region: "India", IPs: []net.IP{{150, 242, 12, 155}, {150, 242, 12, 171}, {150, 242, 12, 187}}}, {Region: "India", IPs: []net.IP{{45, 120, 139, 108}, {45, 120, 139, 109}, {150, 242, 12, 155}, {150, 242, 12, 171}, {150, 242, 12, 187}}},
{Region: "Ireland", IPs: []net.IP{{193, 56, 252, 210}, {193, 56, 252, 226}, {193, 56, 252, 242}}}, {Region: "Ireland", IPs: []net.IP{{193, 56, 252, 210}, {193, 56, 252, 226}, {193, 56, 252, 242}, {193, 56, 252, 250}, {193, 56, 252, 251}, {193, 56, 252, 252}}},
{Region: "Israel", IPs: []net.IP{{31, 168, 172, 145}}}, {Region: "Israel", IPs: []net.IP{{31, 168, 172, 142}, {31, 168, 172, 143}, {31, 168, 172, 145}, {31, 168, 172, 146}}},
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 129}, {156, 146, 41, 193}}}, {Region: "Italy", IPs: []net.IP{{156, 146, 41, 129}, {156, 146, 41, 193}}},
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 1}, {156, 146, 34, 65}}}, {Region: "Japan", IPs: []net.IP{{156, 146, 34, 1}, {156, 146, 34, 65}}},
{Region: "Latvia", IPs: []net.IP{{46, 183, 217, 34}, {46, 183, 218, 130}, {46, 183, 218, 146}}}, {Region: "Latvia", IPs: []net.IP{{46, 183, 217, 34}, {46, 183, 218, 130}, {46, 183, 218, 146}}},
{Region: "Lithuania", IPs: []net.IP{{85, 206, 165, 96}, {85, 206, 165, 112}, {85, 206, 165, 128}}}, {Region: "Lithuania", IPs: []net.IP{{85, 206, 165, 96}, {85, 206, 165, 112}, {85, 206, 165, 128}}},
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 134}, {92, 223, 89, 135}, {92, 223, 89, 136}, {92, 223, 89, 137}, {92, 223, 89, 138}, {92, 223, 89, 140}}}, {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: "Moldova", IPs: []net.IP{{178, 17, 172, 242}, {178, 17, 173, 194}, {178, 175, 128, 34}}}, {Region: "Moldova", IPs: []net.IP{{178, 17, 172, 242}, {178, 17, 173, 194}, {178, 175, 128, 34}}},
{Region: "Netherlands", IPs: []net.IP{{89, 187, 174, 198}, {212, 102, 35, 101}, {212, 102, 35, 102}, {212, 102, 35, 103}, {212, 102, 35, 104}}}, {Region: "Netherlands", IPs: []net.IP{{89, 187, 174, 198}, {212, 102, 35, 101}, {212, 102, 35, 102}, {212, 102, 35, 103}, {212, 102, 35, 104}}},
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 1}, {43, 250, 207, 3}}}, {Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 1}, {43, 250, 207, 3}}},
@@ -162,33 +173,33 @@ func PIAOldServers() []models.PIAServer {
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 34}, {46, 246, 122, 162}}}, {Region: "Norway", IPs: []net.IP{{46, 246, 122, 34}, {46, 246, 122, 162}}},
{Region: "Poland", IPs: []net.IP{{185, 244, 214, 195}, {185, 244, 214, 196}, {185, 244, 214, 197}, {185, 244, 214, 198}, {185, 244, 214, 199}, {185, 244, 214, 200}}}, {Region: "Poland", IPs: []net.IP{{185, 244, 214, 195}, {185, 244, 214, 196}, {185, 244, 214, 197}, {185, 244, 214, 198}, {185, 244, 214, 199}, {185, 244, 214, 200}}},
{Region: "Portugal", IPs: []net.IP{{89, 26, 241, 86}, {89, 26, 241, 102}, {89, 26, 241, 130}}}, {Region: "Portugal", IPs: []net.IP{{89, 26, 241, 86}, {89, 26, 241, 102}, {89, 26, 241, 130}}},
{Region: "Romania", IPs: []net.IP{{86, 105, 25, 69}, {86, 105, 25, 70}, {86, 105, 25, 74}, {86, 105, 25, 75}, {86, 105, 25, 77}, {86, 105, 25, 78}, {89, 33, 8, 38}, {89, 33, 8, 42}, {93, 115, 7, 70}, {94, 176, 148, 34}, {143, 244, 54, 1}, {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}, {188, 240, 220, 26}}}, {Region: "Romania", IPs: []net.IP{{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, 38}, {89, 33, 8, 42}, {93, 115, 7, 70}, {94, 176, 148, 35}, {143, 244, 54, 1}, {185, 45, 12, 126}, {185, 210, 218, 98}, {185, 210, 218, 99}, {185, 210, 218, 100}, {185, 210, 218, 101}, {185, 210, 218, 102}, {185, 210, 218, 105}, {188, 240, 220, 26}}},
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 226}}}, {Region: "Serbia", IPs: []net.IP{{37, 120, 193, 226}}},
{Region: "Singapore", IPs: []net.IP{{156, 146, 56, 193}, {156, 146, 57, 38}, {156, 146, 57, 235}, {156, 146, 57, 244}}}, {Region: "Singapore", IPs: []net.IP{{156, 146, 56, 193}, {156, 146, 57, 38}, {156, 146, 57, 235}, {156, 146, 57, 244}}},
{Region: "Slovakia", IPs: []net.IP{{37, 120, 221, 98}}}, {Region: "Slovakia", IPs: []net.IP{{37, 120, 221, 82}, {37, 120, 221, 98}}},
{Region: "South Africa", IPs: []net.IP{{102, 165, 20, 133}}}, {Region: "South Africa", IPs: []net.IP{{102, 165, 20, 133}}},
{Region: "Spain", IPs: []net.IP{{212, 102, 49, 185}, {212, 102, 49, 251}}}, {Region: "Spain", IPs: []net.IP{{212, 102, 49, 185}, {212, 102, 49, 251}}},
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 253}, {46, 246, 3, 254}}}, {Region: "Sweden", IPs: []net.IP{{46, 246, 3, 254}}},
{Region: "Switzerland", IPs: []net.IP{{156, 146, 62, 193}, {212, 102, 36, 1}, {212, 102, 36, 166}, {212, 102, 37, 240}, {212, 102, 37, 241}, {212, 102, 37, 242}, {212, 102, 37, 243}}}, {Region: "Switzerland", IPs: []net.IP{{156, 146, 62, 193}, {212, 102, 36, 1}, {212, 102, 36, 166}, {212, 102, 37, 240}, {212, 102, 37, 241}, {212, 102, 37, 242}, {212, 102, 37, 243}}},
{Region: "Turkey", IPs: []net.IP{{185, 195, 79, 34}, {185, 195, 79, 82}}}, {Region: "Turkey", IPs: []net.IP{{185, 195, 79, 34}, {185, 195, 79, 82}}},
{Region: "UAE", IPs: []net.IP{{45, 9, 250, 46}}}, {Region: "UAE", IPs: []net.IP{{45, 9, 250, 46}}},
{Region: "UK London", IPs: []net.IP{{37, 235, 96, 198}, {37, 235, 97, 11}, {212, 102, 52, 1}, {212, 102, 52, 134}, {212, 102, 52, 199}, {212, 102, 53, 93}, {212, 102, 53, 129}}}, {Region: "UK London", IPs: []net.IP{{212, 102, 52, 1}}},
{Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 38}, {89, 238, 137, 39}, {89, 238, 139, 52}, {89, 238, 139, 53}, {89, 238, 139, 54}, {89, 238, 139, 55}, {89, 238, 139, 56}, {89, 238, 139, 57}, {89, 238, 139, 58}, {89, 249, 67, 220}}}, {Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 38}, {89, 238, 137, 39}, {89, 238, 139, 52}, {89, 238, 139, 53}, {89, 238, 139, 54}, {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{{143, 244, 36, 58}, {143, 244, 37, 1}, {143, 244, 38, 1}, {143, 244, 38, 60}, {143, 244, 38, 119}}}, {Region: "UK Southampton", IPs: []net.IP{{143, 244, 36, 58}, {143, 244, 37, 1}, {143, 244, 38, 1}, {143, 244, 38, 60}, {143, 244, 38, 119}}},
{Region: "US Atlanta", IPs: []net.IP{{66, 115, 169, 195}, {66, 115, 169, 196}, {66, 115, 169, 197}, {66, 115, 169, 201}, {66, 115, 169, 202}, {66, 115, 169, 204}, {66, 115, 169, 205}, {66, 115, 169, 206}, {66, 115, 169, 209}, {66, 115, 169, 211}, {66, 115, 169, 212}, {66, 115, 169, 213}, {66, 115, 169, 214}, {156, 146, 46, 1}, {156, 146, 46, 134}, {156, 146, 46, 198}, {156, 146, 47, 11}}}, {Region: "US Atlanta", IPs: []net.IP{{156, 146, 46, 1}, {156, 146, 46, 134}, {156, 146, 46, 198}, {156, 146, 47, 11}}},
{Region: "US California", IPs: []net.IP{{37, 235, 108, 144}, {89, 187, 187, 129}, {91, 207, 175, 194}, {91, 207, 175, 195}, {91, 207, 175, 196}, {91, 207, 175, 197}, {91, 207, 175, 198}, {91, 207, 175, 200}, {91, 207, 175, 201}, {91, 207, 175, 202}, {91, 207, 175, 203}, {91, 207, 175, 204}, {91, 207, 175, 206}, {91, 207, 175, 207}, {91, 207, 175, 209}, {91, 207, 175, 211}, {91, 207, 175, 212}}}, {Region: "US California", IPs: []net.IP{{37, 235, 108, 208}, {89, 187, 187, 129}, {89, 187, 187, 162}, {91, 207, 175, 194}, {91, 207, 175, 195}, {91, 207, 175, 197}, {91, 207, 175, 198}, {91, 207, 175, 199}, {91, 207, 175, 200}, {91, 207, 175, 205}, {91, 207, 175, 206}, {91, 207, 175, 207}, {91, 207, 175, 209}, {91, 207, 175, 210}, {91, 207, 175, 212}}},
{Region: "US Chicago", IPs: []net.IP{{156, 146, 50, 1}, {156, 146, 50, 65}, {156, 146, 50, 134}, {156, 146, 50, 198}, {156, 146, 51, 11}, {212, 102, 58, 113}, {212, 102, 59, 54}, {212, 102, 59, 129}}}, {Region: "US Chicago", IPs: []net.IP{{156, 146, 50, 1}, {156, 146, 50, 65}, {156, 146, 50, 134}, {156, 146, 50, 198}, {156, 146, 51, 11}, {212, 102, 58, 113}, {212, 102, 59, 54}, {212, 102, 59, 129}}},
{Region: "US Dallas", IPs: []net.IP{{156, 146, 38, 65}, {156, 146, 38, 161}, {156, 146, 39, 1}, {156, 146, 39, 6}, {156, 146, 52, 6}, {156, 146, 52, 70}, {156, 146, 52, 139}, {156, 146, 52, 203}, {174, 127, 114, 53}, {174, 127, 114, 60}, {174, 127, 114, 68}, {174, 127, 114, 72}, {174, 127, 114, 75}, {174, 127, 114, 77}}}, {Region: "US Dallas", IPs: []net.IP{{156, 146, 38, 65}, {156, 146, 38, 161}, {156, 146, 39, 1}, {156, 146, 39, 6}, {156, 146, 52, 6}, {156, 146, 52, 70}, {156, 146, 52, 139}, {156, 146, 52, 203}}},
{Region: "US Denver", IPs: []net.IP{{174, 128, 225, 2}, {174, 128, 225, 98}, {174, 128, 226, 18}, {174, 128, 227, 226}, {174, 128, 236, 98}, {174, 128, 236, 106}, {174, 128, 242, 234}, {174, 128, 242, 250}, {174, 128, 243, 106}, {174, 128, 244, 66}, {174, 128, 245, 98}, {174, 128, 246, 10}, {174, 128, 250, 26}, {199, 115, 97, 202}, {199, 115, 98, 146}, {199, 115, 98, 226}, {199, 115, 98, 234}, {199, 115, 101, 178}, {199, 115, 102, 146}, {199, 115, 103, 10}}}, {Region: "US Denver", IPs: []net.IP{{70, 39, 77, 130}, {70, 39, 92, 2}, {70, 39, 113, 194}, {174, 128, 225, 2}, {174, 128, 226, 10}, {174, 128, 226, 18}, {174, 128, 227, 2}, {174, 128, 227, 226}, {174, 128, 236, 98}, {174, 128, 242, 234}, {174, 128, 242, 250}, {174, 128, 243, 98}, {174, 128, 244, 74}, {174, 128, 245, 122}, {174, 128, 246, 10}, {199, 115, 98, 146}, {199, 115, 98, 234}, {199, 115, 101, 178}, {199, 115, 101, 186}, {199, 115, 102, 146}}},
{Region: "US East", IPs: []net.IP{{156, 146, 58, 202}, {156, 146, 58, 203}, {156, 146, 58, 204}, {156, 146, 58, 205}, {156, 146, 58, 206}, {156, 146, 58, 207}, {156, 146, 58, 208}, {156, 146, 58, 209}, {194, 59, 251, 5}, {194, 59, 251, 8}, {194, 59, 251, 25}, {194, 59, 251, 30}, {194, 59, 251, 38}, {194, 59, 251, 48}, {194, 59, 251, 49}, {194, 59, 251, 53}, {194, 59, 251, 66}, {194, 59, 251, 78}, {194, 59, 251, 79}, {194, 59, 251, 84}}}, {Region: "US East", IPs: []net.IP{{156, 146, 58, 202}, {156, 146, 58, 203}, {156, 146, 58, 204}, {156, 146, 58, 205}, {156, 146, 58, 207}, {156, 146, 58, 208}, {156, 146, 58, 209}, {193, 37, 253, 115}, {193, 37, 253, 134}, {194, 59, 251, 8}, {194, 59, 251, 11}, {194, 59, 251, 22}, {194, 59, 251, 28}, {194, 59, 251, 56}, {194, 59, 251, 62}, {194, 59, 251, 69}, {194, 59, 251, 82}, {194, 59, 251, 84}, {194, 59, 251, 91}, {194, 59, 251, 112}}},
{Region: "US Florida", IPs: []net.IP{{156, 146, 42, 65}, {156, 146, 42, 134}, {156, 146, 42, 198}, {156, 146, 43, 11}, {156, 146, 43, 75}, {193, 37, 252, 14}, {193, 37, 252, 15}, {193, 37, 252, 16}, {193, 37, 252, 17}, {193, 37, 252, 18}, {193, 37, 252, 19}, {193, 37, 252, 20}, {193, 37, 252, 21}, {193, 37, 252, 22}, {193, 37, 252, 23}, {193, 37, 252, 25}, {193, 37, 252, 26}, {193, 37, 252, 27}, {212, 102, 61, 19}, {212, 102, 61, 83}}}, {Region: "US Florida", IPs: []net.IP{{193, 37, 252, 6}, {193, 37, 252, 7}, {193, 37, 252, 8}, {193, 37, 252, 9}, {193, 37, 252, 10}, {193, 37, 252, 11}, {193, 37, 252, 12}, {193, 37, 252, 14}, {193, 37, 252, 15}, {193, 37, 252, 16}, {193, 37, 252, 17}, {193, 37, 252, 18}, {193, 37, 252, 19}, {193, 37, 252, 20}, {193, 37, 252, 21}, {193, 37, 252, 23}, {193, 37, 252, 24}, {193, 37, 252, 25}, {193, 37, 252, 26}, {193, 37, 252, 27}}},
{Region: "US Houston", IPs: []net.IP{{74, 81, 88, 26}, {74, 81, 88, 42}, {74, 81, 88, 66}, {74, 81, 88, 74}, {205, 251, 148, 90}, {205, 251, 148, 138}, {205, 251, 148, 154}, {205, 251, 148, 178}, {205, 251, 150, 146}, {205, 251, 150, 170}}}, {Region: "US Houston", IPs: []net.IP{{74, 81, 88, 26}, {74, 81, 88, 42}, {74, 81, 88, 66}, {74, 81, 88, 74}, {205, 251, 148, 66}, {205, 251, 148, 90}, {205, 251, 148, 98}, {205, 251, 148, 122}, {205, 251, 148, 130}, {205, 251, 148, 138}, {205, 251, 148, 186}, {205, 251, 150, 146}, {205, 251, 150, 170}}},
{Region: "US Las Vegas", IPs: []net.IP{{79, 110, 53, 34}, {79, 110, 53, 50}, {79, 110, 53, 66}, {79, 110, 53, 82}, {79, 110, 53, 98}, {79, 110, 53, 114}, {79, 110, 53, 130}, {79, 110, 53, 146}, {79, 110, 53, 162}, {79, 110, 53, 178}, {79, 110, 53, 194}, {79, 110, 53, 210}}}, {Region: "US Las Vegas", IPs: []net.IP{{79, 110, 53, 50}, {79, 110, 53, 66}, {79, 110, 53, 98}, {79, 110, 53, 114}, {79, 110, 53, 130}, {79, 110, 53, 146}, {79, 110, 53, 162}, {79, 110, 53, 178}, {79, 110, 53, 194}, {79, 110, 53, 210}, {162, 251, 236, 7}, {199, 127, 56, 83}, {199, 127, 56, 84}, {199, 127, 56, 87}, {199, 127, 56, 89}, {199, 127, 56, 90}}},
{Region: "US New York City", IPs: []net.IP{{156, 146, 36, 225}, {156, 146, 55, 198}}}, {Region: "US New York City", IPs: []net.IP{{156, 146, 36, 225}, {156, 146, 37, 129}, {156, 146, 58, 1}, {156, 146, 58, 134}}},
{Region: "US Seattle", IPs: []net.IP{{84, 17, 41, 96}, {156, 146, 48, 65}, {156, 146, 48, 135}, {156, 146, 48, 200}, {156, 146, 49, 13}, {212, 102, 46, 129}, {212, 102, 46, 193}, {212, 102, 47, 134}}}, {Region: "US Seattle", IPs: []net.IP{{156, 146, 48, 65}, {156, 146, 48, 135}, {156, 146, 48, 200}, {156, 146, 49, 13}, {212, 102, 46, 129}, {212, 102, 46, 193}, {212, 102, 47, 134}}},
{Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 133}, {199, 116, 118, 148}, {199, 116, 118, 167}, {199, 116, 118, 172}, {199, 116, 118, 173}, {199, 116, 118, 174}, {199, 116, 118, 185}, {199, 116, 118, 198}, {199, 116, 118, 202}, {199, 116, 118, 210}, {199, 116, 118, 212}, {199, 116, 118, 215}, {199, 116, 118, 217}, {199, 116, 118, 219}, {199, 116, 118, 220}, {199, 116, 118, 223}, {199, 116, 118, 237}, {199, 116, 118, 239}, {199, 116, 118, 240}, {199, 116, 118, 249}}}, {Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 130}, {199, 116, 118, 132}, {199, 116, 118, 134}, {199, 116, 118, 136}, {199, 116, 118, 145}, {199, 116, 118, 148}, {199, 116, 118, 149}, {199, 116, 118, 157}, {199, 116, 118, 166}, {199, 116, 118, 169}, {199, 116, 118, 172}}},
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 52}, {70, 32, 0, 53}, {70, 32, 0, 59}, {70, 32, 0, 60}, {70, 32, 0, 61}, {70, 32, 0, 64}, {70, 32, 0, 67}, {70, 32, 0, 68}, {70, 32, 0, 69}, {70, 32, 0, 70}, {70, 32, 0, 103}, {70, 32, 0, 106}, {70, 32, 0, 107}, {70, 32, 0, 114}, {70, 32, 0, 116}, {70, 32, 0, 120}, {70, 32, 0, 122}, {70, 32, 0, 168}, {70, 32, 0, 172}, {70, 32, 0, 173}}}, {Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 46}, {70, 32, 0, 51}, {70, 32, 0, 53}, {70, 32, 0, 62}, {70, 32, 0, 64}, {70, 32, 0, 68}, {70, 32, 0, 69}, {70, 32, 0, 72}, {70, 32, 0, 76}, {70, 32, 0, 77}, {70, 32, 0, 106}, {70, 32, 0, 107}, {70, 32, 0, 114}, {70, 32, 0, 116}, {70, 32, 0, 120}, {70, 32, 0, 167}, {70, 32, 0, 168}, {70, 32, 0, 170}, {70, 32, 0, 172}, {70, 32, 0, 173}}},
{Region: "US West", IPs: []net.IP{{104, 200, 151, 7}, {104, 200, 151, 8}, {104, 200, 151, 9}, {104, 200, 151, 11}, {104, 200, 151, 13}, {104, 200, 151, 16}, {104, 200, 151, 17}, {104, 200, 151, 20}, {104, 200, 151, 21}, {104, 200, 151, 46}, {104, 200, 151, 47}, {104, 200, 151, 50}, {104, 200, 151, 53}, {104, 200, 151, 56}, {104, 200, 151, 59}, {104, 200, 151, 61}, {104, 200, 151, 72}, {104, 200, 151, 74}, {104, 200, 151, 78}, {104, 200, 151, 81}}}, {Region: "US West", IPs: []net.IP{{184, 170, 241, 130}, {184, 170, 241, 194}, {184, 170, 242, 135}, {184, 170, 242, 199}}},
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 10}, {62, 149, 20, 40}}}, {Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 10}, {62, 149, 20, 40}}},
} }
} }

View File

@@ -21,13 +21,13 @@ func GetAllServers() (allServers models.AllServers) {
Servers: NordvpnServers(), Servers: NordvpnServers(),
}, },
Pia: models.PiaServers{ Pia: models.PiaServers{
Version: 1, Version: 2,
Timestamp: 1599323261, Timestamp: 1602531173,
Servers: PIAServers(), Servers: PIAServers(),
}, },
PiaOld: models.PiaServers{ PiaOld: models.PiaOldServers{
Version: 1, Version: 1,
Timestamp: 1600458645, Timestamp: 1602523433,
Servers: PIAOldServers(), Servers: PIAOldServers(),
}, },
Purevpn: models.PurevpnServers{ Purevpn: models.PurevpnServers{

View File

@@ -27,7 +27,7 @@ func Test_versions(t *testing.T) {
assert.Equal(t, "e8eLGRpb1sNX8mDNPOjA6g", digestServerModelVersion(t, models.CyberghostServer{}, allServers.Cyberghost.Version)) assert.Equal(t, "e8eLGRpb1sNX8mDNPOjA6g", digestServerModelVersion(t, models.CyberghostServer{}, allServers.Cyberghost.Version))
assert.Equal(t, "4yL2lFcxXd/l1ByxBQ7d3g", digestServerModelVersion(t, models.MullvadServer{}, allServers.Mullvad.Version)) assert.Equal(t, "4yL2lFcxXd/l1ByxBQ7d3g", digestServerModelVersion(t, models.MullvadServer{}, allServers.Mullvad.Version))
assert.Equal(t, "fjzfUqJH0KvetGRdZYEtOg", digestServerModelVersion(t, models.NordvpnServer{}, allServers.Nordvpn.Version)) assert.Equal(t, "fjzfUqJH0KvetGRdZYEtOg", digestServerModelVersion(t, models.NordvpnServer{}, allServers.Nordvpn.Version))
assert.Equal(t, "gYO+bJZCtQvxVk2dTi5d5Q", digestServerModelVersion(t, models.PIAServer{}, allServers.Pia.Version)) assert.Equal(t, "1Ux7clCAJI6fwj0O61Dtpg", digestServerModelVersion(t, models.PIAServer{}, allServers.Pia.Version))
assert.Equal(t, "EZ/SBXQOCS/iJU7A9yc7vg", digestServerModelVersion(t, models.PurevpnServer{}, allServers.Purevpn.Version)) assert.Equal(t, "EZ/SBXQOCS/iJU7A9yc7vg", digestServerModelVersion(t, models.PurevpnServer{}, allServers.Purevpn.Version))
assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.SurfsharkServer{}, allServers.Surfshark.Version)) assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.SurfsharkServer{}, allServers.Surfshark.Version))
assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.VyprvpnServer{}, allServers.Vyprvpn.Version)) assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.VyprvpnServer{}, allServers.Vyprvpn.Version))
@@ -50,8 +50,8 @@ func Test_timestamps(t *testing.T) {
assert.Equal(t, "EFMpdq2b9COLevjXmje5zg", digestServersTimestamp(t, allServers.Cyberghost.Servers, allServers.Cyberghost.Timestamp)) assert.Equal(t, "EFMpdq2b9COLevjXmje5zg", digestServersTimestamp(t, allServers.Cyberghost.Servers, allServers.Cyberghost.Timestamp))
assert.Equal(t, "EU4fTzD7jWC9N5kmN5bOEg", digestServersTimestamp(t, allServers.Mullvad.Servers, allServers.Mullvad.Timestamp)) assert.Equal(t, "EU4fTzD7jWC9N5kmN5bOEg", digestServersTimestamp(t, allServers.Mullvad.Servers, allServers.Mullvad.Timestamp))
assert.Equal(t, "OLI62FoTf2wis25Nw4FLpg", digestServersTimestamp(t, allServers.Nordvpn.Servers, allServers.Nordvpn.Timestamp)) assert.Equal(t, "OLI62FoTf2wis25Nw4FLpg", digestServersTimestamp(t, allServers.Nordvpn.Servers, allServers.Nordvpn.Timestamp))
assert.Equal(t, "hAjEIo6FIrUsJuRmKOKPzA", digestServersTimestamp(t, allServers.Pia.Servers, allServers.Pia.Timestamp)) assert.Equal(t, "beZCOXNWxzrPsUWCyQM99A", digestServersTimestamp(t, allServers.Pia.Servers, allServers.Pia.Timestamp))
assert.Equal(t, "CKszzgA7YX5zqxQGiiOL9g", digestServersTimestamp(t, allServers.PiaOld.Servers, allServers.PiaOld.Timestamp)) assert.Equal(t, "e8mWsWynkSUGiJLvjALRvQ", digestServersTimestamp(t, allServers.PiaOld.Servers, allServers.PiaOld.Timestamp))
assert.Equal(t, "kwJdVWTiBOspfrRwZIA+Sg", digestServersTimestamp(t, allServers.Purevpn.Servers, allServers.Purevpn.Timestamp)) assert.Equal(t, "kwJdVWTiBOspfrRwZIA+Sg", digestServersTimestamp(t, allServers.Purevpn.Servers, allServers.Purevpn.Timestamp))
assert.Equal(t, "q28ju2KJqLhrggJTTjXSiw", digestServersTimestamp(t, allServers.Surfshark.Servers, allServers.Surfshark.Timestamp)) assert.Equal(t, "q28ju2KJqLhrggJTTjXSiw", digestServersTimestamp(t, allServers.Surfshark.Servers, allServers.Surfshark.Timestamp))
assert.Equal(t, "KdIQWi2tYUM4aMXvWfVBEg", digestServersTimestamp(t, allServers.Vyprvpn.Servers, allServers.Vyprvpn.Timestamp)) assert.Equal(t, "KdIQWi2tYUM4aMXvWfVBEg", digestServersTimestamp(t, allServers.Vyprvpn.Servers, allServers.Vyprvpn.Timestamp))

View File

@@ -2,9 +2,9 @@ package constants
const ( const (
// Announcement is a message announcement // Announcement is a message announcement
Announcement = "Update servers information see https://github.com/qdm12/gluetun/wiki/Update-servers-information" Announcement = "Port forwarding is working for PIA v4 servers"
// AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd // AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd
AnnouncementExpiration = "2020-10-10" AnnouncementExpiration = "2020-11-15"
) )
const ( const (

View File

@@ -34,6 +34,8 @@ type looper struct {
start chan struct{} start chan struct{}
stop chan struct{} stop chan struct{}
updateTicker chan struct{} updateTicker chan struct{}
timeNow func() time.Time
timeSince func(time.Time) time.Duration
} }
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger, func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
@@ -49,6 +51,8 @@ func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
start: make(chan struct{}), start: make(chan struct{}),
stop: make(chan struct{}), stop: make(chan struct{}),
updateTicker: make(chan struct{}), updateTicker: make(chan struct{}),
timeNow: time.Now,
timeSince: time.Since,
} }
} }
@@ -285,24 +289,45 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) { func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
ticker := time.NewTicker(time.Hour) // Timer that acts as a ticker
timer := time.NewTimer(time.Hour)
timer.Stop()
timerIsStopped := true
settings := l.GetSettings() settings := l.GetSettings()
if settings.UpdatePeriod > 0 { if settings.UpdatePeriod > 0 {
ticker = time.NewTicker(settings.UpdatePeriod) timer.Reset(settings.UpdatePeriod)
} else { timerIsStopped = false
ticker.Stop()
} }
lastTick := time.Unix(0, 0)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
ticker.Stop() if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return return
case <-ticker.C: case <-timer.C:
lastTick = l.timeNow()
l.restart <- struct{}{} l.restart <- struct{}{}
settings := l.GetSettings()
timer.Reset(settings.UpdatePeriod)
case <-l.updateTicker: case <-l.updateTicker:
ticker.Stop() if !timer.Stop() {
period := l.GetSettings().UpdatePeriod <-timer.C
ticker = time.NewTicker(period) }
timerIsStopped = true
settings := l.GetSettings()
newUpdatePeriod := settings.UpdatePeriod
if newUpdatePeriod == 0 {
continue
}
var waited time.Duration
if lastTick.UnixNano() != 0 {
waited = l.timeSince(lastTick)
}
leftToWait := newUpdatePeriod - waited
timer.Reset(leftToWait)
timerIsStopped = false
} }
} }
} }

View File

@@ -85,8 +85,8 @@ func (c *configurator) enable(ctx context.Context) (err error) { //nolint:gocogn
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil { if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err) return fmt.Errorf("cannot enable firewall: %w", err)
} }
for _, conn := range c.vpnConnections { if c.vpnConnection.IP != nil {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, conn, remove); err != nil { if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err) return fmt.Errorf("cannot enable firewall: %w", err)
} }
} }

View File

@@ -16,7 +16,7 @@ import (
type Configurator interface { type Configurator interface {
Version(ctx context.Context) (string, error) Version(ctx context.Context) (string, error)
SetEnabled(ctx context.Context, enabled bool) (err error) SetEnabled(ctx context.Context, enabled bool) (err error)
SetVPNConnections(ctx context.Context, connections []models.OpenVPNConnection) (err error) SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error)
SetAllowedSubnets(ctx context.Context, subnets []net.IPNet) (err error) SetAllowedSubnets(ctx context.Context, subnets []net.IPNet) (err error)
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error) SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
RemoveAllowedPort(ctx context.Context, port uint16) (err error) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
@@ -39,7 +39,7 @@ type configurator struct { //nolint:maligned
// State // State
enabled bool enabled bool
vpnConnections []models.OpenVPNConnection vpnConnection models.OpenVPNConnection
allowedSubnets []net.IPNet allowedSubnets []net.IPNet
allowedInputPorts map[uint16]string // port to interface mapping allowedInputPorts map[uint16]string // port to interface mapping
stateMutex sync.Mutex stateMutex sync.Mutex

View File

@@ -7,95 +7,33 @@ import (
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
) )
func (c *configurator) SetVPNConnections(ctx context.Context, connections []models.OpenVPNConnection) (err error) { func (c *configurator) SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error) {
c.stateMutex.Lock() c.stateMutex.Lock()
defer c.stateMutex.Unlock() defer c.stateMutex.Unlock()
if !c.enabled { if !c.enabled {
c.logger.Info("firewall disabled, only updating VPN connections internal list") c.logger.Info("firewall disabled, only updating internal VPN connection")
c.vpnConnections = make([]models.OpenVPNConnection, len(connections)) c.vpnConnection = connection
copy(c.vpnConnections, connections)
return nil return nil
} }
c.logger.Info("setting VPN connections through firewall...") c.logger.Info("setting VPN connection through firewall...")
connectionsToAdd := findConnectionsToAdd(c.vpnConnections, connections) if c.vpnConnection.Equal(connection) {
connectionsToRemove := findConnectionsToRemove(c.vpnConnections, connections)
if len(connectionsToAdd) == 0 && len(connectionsToRemove) == 0 {
return nil return nil
} }
c.removeConnections(ctx, connectionsToRemove, c.defaultInterface) remove := true
if err := c.addConnections(ctx, connectionsToAdd, c.defaultInterface); err != nil { if c.vpnConnection.IP != nil {
return fmt.Errorf("cannot set VPN connections through firewall: %w", err) if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
}
return nil
}
func removeConnectionFromConnections(connections []models.OpenVPNConnection, connection models.OpenVPNConnection) []models.OpenVPNConnection {
L := len(connections)
for i := range connections {
if connection.Equal(connections[i]) {
connections[i] = connections[L-1]
connections = connections[:L-1]
break
}
}
return connections
}
func findConnectionsToAdd(oldConnections, newConnections []models.OpenVPNConnection) (connectionsToAdd []models.OpenVPNConnection) {
for _, newConnection := range newConnections {
found := false
for _, oldConnection := range oldConnections {
if oldConnection.Equal(newConnection) {
found = true
break
}
}
if !found {
connectionsToAdd = append(connectionsToAdd, newConnection)
}
}
return connectionsToAdd
}
func findConnectionsToRemove(oldConnections, newConnections []models.OpenVPNConnection) (connectionsToRemove []models.OpenVPNConnection) {
for _, oldConnection := range oldConnections {
found := false
for _, newConnection := range newConnections {
if oldConnection.Equal(newConnection) {
found = true
break
}
}
if !found {
connectionsToRemove = append(connectionsToRemove, oldConnection)
}
}
return connectionsToRemove
}
func (c *configurator) removeConnections(ctx context.Context, connections []models.OpenVPNConnection, defaultInterface string) {
for _, conn := range connections {
const remove = true
if err := c.acceptOutputTrafficToVPN(ctx, defaultInterface, conn, remove); err != nil {
c.logger.Error("cannot remove outdated VPN connection through firewall: %s", err) c.logger.Error("cannot remove outdated VPN connection through firewall: %s", err)
continue
} }
c.vpnConnections = removeConnectionFromConnections(c.vpnConnections, conn)
} }
} c.vpnConnection = models.OpenVPNConnection{}
remove = false
func (c *configurator) addConnections(ctx context.Context, connections []models.OpenVPNConnection, defaultInterface string) error { if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
const remove = false return fmt.Errorf("cannot set VPN connection through firewall: %w", err)
for _, conn := range connections {
if err := c.acceptOutputTrafficToVPN(ctx, defaultInterface, conn, remove); err != nil {
return err
}
c.vpnConnections = append(c.vpnConnections, conn)
} }
c.vpnConnection = connection
return nil return nil
} }

View File

@@ -0,0 +1,29 @@
package logging
import (
"fmt"
"time"
)
func FormatDuration(duration time.Duration) string {
switch {
case duration < time.Minute:
seconds := int(duration.Round(time.Second).Seconds())
if seconds < 2 {
return fmt.Sprintf("%d second", seconds)
}
return fmt.Sprintf("%d seconds", seconds)
case duration <= time.Hour:
minutes := int(duration.Round(time.Minute).Minutes())
if minutes == 1 {
return "1 minute"
}
return fmt.Sprintf("%d minutes", minutes)
case duration < 48*time.Hour:
hours := int(duration.Truncate(time.Hour).Hours())
return fmt.Sprintf("%d hours", hours)
default:
days := int(duration.Truncate(time.Hour).Hours() / 24)
return fmt.Sprintf("%d days", days)
}
}

View File

@@ -1,4 +1,4 @@
package version package logging
import ( import (
"testing" "testing"
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_formatDuration(t *testing.T) { func Test_FormatDuration(t *testing.T) {
t.Parallel() t.Parallel()
testCases := map[string]struct { testCases := map[string]struct {
duration time.Duration duration time.Duration
@@ -57,7 +57,7 @@ func Test_formatDuration(t *testing.T) {
testCase := testCase testCase := testCase
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
s := formatDuration(testCase.duration) s := FormatDuration(testCase.duration)
assert.Equal(t, testCase.s, s) assert.Equal(t, testCase.s, s)
}) })
} }

View File

@@ -90,6 +90,7 @@ func (p *ProviderSettings) String() string {
settingsList = append(settingsList, settingsList = append(settingsList,
"Region: "+p.ServerSelection.Region, "Region: "+p.ServerSelection.Region,
"Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset, "Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset,
"Port forwarding: "+p.PortForwarding.String(),
) )
case "mullvad": case "mullvad":
settingsList = append(settingsList, settingsList = append(settingsList,

View File

@@ -8,11 +8,32 @@ import (
) )
type PIAServer struct { type PIAServer struct {
Region string `json:"region"`
PortForward bool `json:"port_forward"`
OpenvpnUDP PIAServerOpenvpn `json:"openvpn_udp"`
OpenvpnTCP PIAServerOpenvpn `json:"openvpn_tcp"`
}
type PIAServerOpenvpn struct {
IPs []net.IP `json:"ips"`
CN string `json:"cn"`
}
func (p *PIAServerOpenvpn) String() string {
return fmt.Sprintf("models.PIAServerOpenvpn{CN: %q, IPs: %s}", p.CN, goStringifyIPs(p.IPs))
}
func (p *PIAServer) String() string {
return fmt.Sprintf("{Region: %q, PortForward: %t, OpenvpnUDP: %s, OpenvpnTCP: %s}",
p.Region, p.PortForward, p.OpenvpnUDP.String(), p.OpenvpnTCP.String())
}
type PIAOldServer struct {
IPs []net.IP `json:"ips"` IPs []net.IP `json:"ips"`
Region string `json:"region"` Region string `json:"region"`
} }
func (p *PIAServer) String() string { func (p *PIAOldServer) String() string {
return fmt.Sprintf("{Region: %q, IPs: %s}", p.Region, goStringifyIPs(p.IPs)) return fmt.Sprintf("{Region: %q, IPs: %s}", p.Region, goStringifyIPs(p.IPs))
} }

View File

@@ -7,18 +7,18 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_PIAServer_String(t *testing.T) { func Test_PIAOldServer_String(t *testing.T) {
t.Parallel() t.Parallel()
testCases := map[string]struct { testCases := map[string]struct {
server PIAServer server PIAOldServer
s string s string
}{ }{
"no ips": { "no ips": {
server: PIAServer{Region: "a b"}, server: PIAOldServer{Region: "a b"},
s: `{Region: "a b", IPs: []net.IP{}}`, s: `{Region: "a b", IPs: []net.IP{}}`,
}, },
"with ips": { "with ips": {
server: PIAServer{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}, server: PIAOldServer{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}},
s: `{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}`, s: `{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}`,
}, },
} }

View File

@@ -5,7 +5,7 @@ type AllServers struct {
Cyberghost CyberghostServers `json:"cyberghost"` Cyberghost CyberghostServers `json:"cyberghost"`
Mullvad MullvadServers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Nordvpn NordvpnServers `json:"nordvpn"` Nordvpn NordvpnServers `json:"nordvpn"`
PiaOld PiaServers `json:"piaOld"` PiaOld PiaOldServers `json:"piaOld"`
Pia PiaServers `json:"pia"` Pia PiaServers `json:"pia"`
Purevpn PurevpnServers `json:"purevpn"` Purevpn PurevpnServers `json:"purevpn"`
Surfshark SurfsharkServers `json:"surfshark"` Surfshark SurfsharkServers `json:"surfshark"`
@@ -28,6 +28,11 @@ type NordvpnServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []NordvpnServer `json:"servers"` Servers []NordvpnServer `json:"servers"`
} }
type PiaOldServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PIAOldServer `json:"servers"`
}
type PiaServers struct { type PiaServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -2,7 +2,8 @@ package openvpn
import ( import (
"context" "context"
"fmt" "net"
"net/http"
"sync" "sync"
"time" "time"
@@ -10,17 +11,17 @@ import (
"github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/golibs/command" "github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
) )
type Looper interface { type Looper interface {
Run(ctx context.Context, wg *sync.WaitGroup) Run(ctx context.Context, wg *sync.WaitGroup)
Restart() Restart()
PortForward() PortForward(vpnGatewayIP net.IP)
GetSettings() (settings settings.OpenVPN) GetSettings() (settings settings.OpenVPN)
SetSettings(settings settings.OpenVPN) SetSettings(settings settings.OpenVPN)
GetPortForwarded() (portForwarded uint16) GetPortForwarded() (portForwarded uint16)
@@ -40,23 +41,24 @@ type looper struct {
uid int uid int
gid int gid int
// Configurators // Configurators
conf Configurator conf Configurator
fw firewall.Configurator fw firewall.Configurator
routing routing.Routing
// Other objects // Other objects
logger logging.Logger logger, pfLogger logging.Logger
client network.Client client *http.Client
fileManager files.FileManager fileManager files.FileManager
streamMerger command.StreamMerger streamMerger command.StreamMerger
cancel context.CancelFunc cancel context.CancelFunc
// Internal channels // Internal channels
restart chan struct{} restart chan struct{}
portForwardSignals chan struct{} portForwardSignals chan net.IP
} }
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN, func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
uid, gid int, allServers models.AllServers, uid, gid int, allServers models.AllServers,
conf Configurator, fw firewall.Configurator, conf Configurator, fw firewall.Configurator, routing routing.Routing,
logger logging.Logger, client network.Client, fileManager files.FileManager, logger logging.Logger, client *http.Client, fileManager files.FileManager,
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper { streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
return &looper{ return &looper{
provider: provider, provider: provider,
@@ -66,18 +68,20 @@ func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
allServers: allServers, allServers: allServers,
conf: conf, conf: conf,
fw: fw, fw: fw,
routing: routing,
logger: logger.WithPrefix("openvpn: "), logger: logger.WithPrefix("openvpn: "),
pfLogger: logger.WithPrefix("port forwarding: "),
client: client, client: client,
fileManager: fileManager, fileManager: fileManager,
streamMerger: streamMerger, streamMerger: streamMerger,
cancel: cancel, cancel: cancel,
restart: make(chan struct{}), restart: make(chan struct{}),
portForwardSignals: make(chan struct{}), portForwardSignals: make(chan net.IP),
} }
} }
func (l *looper) Restart() { l.restart <- struct{}{} } func (l *looper) Restart() { l.restart <- struct{}{} }
func (l *looper) PortForward() { l.portForwardSignals <- struct{}{} } func (l *looper) PortForward(vpnGateway net.IP) { l.portForwardSignals <- vpnGateway }
func (l *looper) GetSettings() (settings settings.OpenVPN) { func (l *looper) GetSettings() (settings settings.OpenVPN) {
l.settingsMutex.RLock() l.settingsMutex.RLock()
@@ -109,16 +113,16 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
for ctx.Err() == nil { for ctx.Err() == nil {
settings := l.GetSettings() settings := l.GetSettings()
l.allServersMutex.RLock() l.allServersMutex.RLock()
providerConf := provider.New(l.provider, l.allServers) providerConf := provider.New(l.provider, l.allServers, time.Now)
l.allServersMutex.RUnlock() l.allServersMutex.RUnlock()
connections, err := providerConf.GetOpenVPNConnections(settings.Provider.ServerSelection) connection, err := providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
if err != nil { if err != nil {
l.logger.Error(err) l.logger.Error(err)
l.cancel() l.cancel()
return return
} }
lines := providerConf.BuildConf( lines := providerConf.BuildConf(
connections, connection,
settings.Verbosity, settings.Verbosity,
l.uid, l.uid,
l.gid, l.gid,
@@ -139,7 +143,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
return return
} }
if err := l.fw.SetVPNConnections(ctx, connections); err != nil { if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
l.logger.Error(err) l.logger.Error(err)
l.cancel() l.cancel()
return return
@@ -158,10 +162,12 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
go func(ctx context.Context) { go func(ctx context.Context) {
for { for {
select { select {
// TODO have a way to disable pf with a context
case <-ctx.Done(): case <-ctx.Done():
return return
case <-l.portForwardSignals: case gateway := <-l.portForwardSignals:
l.portForward(ctx, providerConf, l.client) wg.Add(1)
go l.portForward(ctx, wg, providerConf, l.client, gateway)
} }
} }
}(openvpnCtx) }(openvpnCtx)
@@ -200,43 +206,25 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
<-ctx.Done() <-ctx.Done()
} }
func (l *looper) portForward(ctx context.Context, providerConf provider.Provider, client network.Client) { // portForward is a blocking operation which may or may not be infinite.
// You should therefore always call it in a goroutine
func (l *looper) portForward(ctx context.Context, wg *sync.WaitGroup,
providerConf provider.Provider, client *http.Client, gateway net.IP) {
defer wg.Done()
settings := l.GetSettings() settings := l.GetSettings()
if !settings.Provider.PortForwarding.Enabled { if !settings.Provider.PortForwarding.Enabled {
return return
} }
var port uint16 syncState := func(port uint16) (pfFilepath models.Filepath) {
err := fmt.Errorf("") l.portForwardedMutex.Lock()
for err != nil { l.portForwarded = port
if ctx.Err() != nil { l.portForwardedMutex.Unlock()
return settings := l.GetSettings()
} return settings.Provider.PortForwarding.Filepath
port, err = providerConf.GetPortForward(client)
if err != nil {
l.logAndWait(ctx, err)
}
}
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)
err = l.fileManager.WriteLinesToFile(
string(filepath), []string{fmt.Sprintf("%d", port)},
files.Ownership(l.uid, l.gid), files.Permissions(0400),
)
if err != nil {
l.logger.Error(err)
} }
providerConf.PortForward(ctx,
client, l.fileManager, l.pfLogger,
gateway, l.fw, syncState)
} }
func (l *looper) GetPortForwarded() (portForwarded uint16) { func (l *looper) GetPortForwarded() (portForwarded uint16) {

View File

@@ -53,9 +53,8 @@ func (r *reader) GetOpenVPNRoot() (root bool, err error) {
return r.envParams.GetYesNo("OPENVPN_ROOT", libparams.Default("no")) return r.envParams.GetYesNo("OPENVPN_ROOT", libparams.Default("no"))
} }
// GetTargetIP obtains the IP address to choose from the list of IP addresses // GetTargetIP obtains the IP address to override over the list of IP addresses filtered
// available for a particular region, from the environment variable // from the environment variable OPENVPN_TARGET_IP
// OPENVPN_TARGET_IP
func (r *reader) GetTargetIP() (ip net.IP, err error) { func (r *reader) GetTargetIP() (ip net.IP, err error) {
s, err := r.envParams.GetEnv("OPENVPN_TARGET_IP") s, err := r.envParams.GetEnv("OPENVPN_TARGET_IP")
if len(s) == 0 { if len(s) == 0 {

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type cyberghost struct { type cyberghost struct {
servers []models.CyberghostServer servers []models.CyberghostServer
randSource rand.Source
} }
func newCyberghost(servers []models.CyberghostServer) *cyberghost { func newCyberghost(servers []models.CyberghostServer, timeNow timeNowFunc) *cyberghost {
return &cyberghost{ return &cyberghost{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -34,36 +42,27 @@ func (c *cyberghost) filterServers(region, group string) (servers []models.Cyber
return servers return servers
} }
func (c *cyberghost) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
servers := c.filterServers(selection.Region, selection.Group) if selection.TargetIP != nil {
if len(servers) == 0 { return models.OpenVPNConnection{IP: selection.TargetIP, Port: 443, Protocol: selection.Protocol}, nil
return nil, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group)
} }
servers := c.filterServers(selection.Region, selection.Group)
if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group)
}
var connections []models.OpenVPNConnection
for _, server := range servers { for _, server := range servers {
for _, IP := range server.IPs { for _, IP := range server.IPs {
if selection.TargetIP != nil { connections = append(connections, models.OpenVPNConnection{IP: IP, Port: 443, Protocol: selection.Protocol})
if selection.TargetIP.Equal(IP) {
return []models.OpenVPNConnection{{IP: IP, Port: 443, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: 443, Protocol: selection.Protocol})
}
} }
} }
if selection.TargetIP != nil { return pickRandomConnection(connections, c.randSource), 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 (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -97,7 +96,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", connections[0].Protocol), fmt.Sprintf("proto %s", connection.Protocol),
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth), fmt.Sprintf("auth %s", auth),
} }
@@ -107,9 +107,6 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
if !root { if !root {
lines = append(lines, "user nonrootuser") lines = append(lines, "user nonrootuser")
} }
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
}
lines = append(lines, []string{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -135,6 +132,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
return lines return lines
} }
func (c *cyberghost) GetPortForward(client network.Client) (port uint16, err error) { func (c *cyberghost) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for cyberghost") panic("port forwarding is not supported for cyberghost")
} }

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type mullvad struct { type mullvad struct {
servers []models.MullvadServer servers []models.MullvadServer
randSource rand.Source
} }
func newMullvad(servers []models.MullvadServer) *mullvad { func newMullvad(servers []models.MullvadServer, timeNow timeNowFunc) *mullvad {
return &mullvad{ return &mullvad{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -39,45 +47,36 @@ func (m *mullvad) filterServers(country, city, isp string) (servers []models.Mul
return servers return servers
} }
func (m *mullvad) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
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)
}
var defaultPort uint16 = 1194 var defaultPort uint16 = 1194
if selection.Protocol == constants.TCP { if selection.Protocol == constants.TCP {
defaultPort = 443 defaultPort = 443
} }
port := defaultPort
for _, server := range servers { if selection.CustomPort > 0 {
port := defaultPort port = selection.CustomPort
if selection.CustomPort > 0 {
port = selection.CustomPort
}
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 { if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP) return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
} }
if len(connections) > 64 { servers := m.filterServers(selection.Country, selection.City, selection.ISP)
connections = connections[:64] if len(servers) == 0 {
return connection, fmt.Errorf("no server found for country %q, city %q and ISP %q", selection.Country, selection.City, selection.ISP)
} }
return connections, nil var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
return pickRandomConnection(connections, m.randSource), nil
} }
func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { func (m *mullvad) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -108,7 +107,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", connections[0].Protocol), fmt.Sprintf("proto %s", connection.Protocol),
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
} }
if extras.OpenVPNIPv6 { if extras.OpenVPNIPv6 {
@@ -120,9 +120,6 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
if !root { if !root {
lines = append(lines, "user nonrootuser") lines = append(lines, "user nonrootuser")
} }
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
}
lines = append(lines, []string{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -134,6 +131,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines return lines
} }
func (m *mullvad) GetPortForward(client network.Client) (port uint16, err error) { func (m *mullvad) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for mullvad") panic("port forwarding is not supported for mullvad")
} }

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type nordvpn struct { type nordvpn struct {
servers []models.NordvpnServer servers []models.NordvpnServer
randSource rand.Source
} }
func newNordvpn(servers []models.NordvpnServer) *nordvpn { func newNordvpn(servers []models.NordvpnServer, timeNow timeNowFunc) *nordvpn {
return &nordvpn{ return &nordvpn{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -40,12 +48,7 @@ func (n *nordvpn) filterServers(region string, protocol models.NetworkProtocol,
return servers return servers
} }
func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
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 var port uint16
switch { switch {
case selection.Protocol == constants.UDP: case selection.Protocol == constants.UDP:
@@ -53,31 +56,27 @@ func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
case selection.Protocol == constants.TCP: case selection.Protocol == constants.TCP:
port = 443 port = 443
default: default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol) return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
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 { if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP) return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
} }
if len(connections) > 64 { servers := n.filterServers(selection.Region, selection.Protocol, selection.Number)
connections = connections[:64] if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number)
} }
return connections, nil connections := make([]models.OpenVPNConnection, len(servers))
for i := range servers {
connections = append(connections, models.OpenVPNConnection{IP: servers[i].IP, Port: port, Protocol: selection.Protocol})
}
return pickRandomConnection(connections, n.randSource), nil
} }
func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -114,16 +113,14 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", string(connections[0].Protocol)), fmt.Sprintf("proto %s", string(connection.Protocol)),
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth), fmt.Sprintf("auth %s", auth),
} }
if !root { if !root {
lines = append(lines, "user nonrootuser") 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{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -142,6 +139,8 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines return lines
} }
func (n *nordvpn) GetPortForward(client network.Client) (port uint16, err error) { func (n *nordvpn) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for nordvpn") panic("port forwarding is not supported for nordvpn")
} }

View File

@@ -1,93 +1,14 @@
package provider package provider
import ( import (
"encoding/hex"
"encoding/json"
"fmt" "fmt"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/crypto/random"
"github.com/qdm12/golibs/network"
) )
type pia struct { func buildPIAConf(connection models.OpenVPNConnection, verbosity int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
random random.Random
servers []models.PIAServer
}
func newPrivateInternetAccess(servers []models.PIAServer) *pia {
return &pia{
random: random.NewRandom(),
servers: servers,
}
}
func (p *pia) filterServers(region string) (servers []models.PIAServer) {
if len(region) == 0 {
return p.servers
}
for _, server := range p.servers {
if strings.EqualFold(server.Region, region) {
return []models.PIAServer{server}
}
}
return nil
}
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:
switch selection.EncryptionPreset {
case constants.PIAEncryptionPresetNormal:
port = 502
case constants.PIAEncryptionPresetStrong:
port = 501
}
case constants.UDP:
switch selection.EncryptionPreset {
case constants.PIAEncryptionPresetNormal:
port = 1198
case constants.PIAEncryptionPresetStrong:
port = 1197
}
}
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 _, 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 (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
var X509CRL, certificate string var X509CRL, certificate string
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal { if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
if len(cipher) == 0 { if len(cipher) == 0 {
@@ -131,7 +52,8 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", connections[0].Protocol), fmt.Sprintf("proto %s", connection.Protocol),
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth), fmt.Sprintf("auth %s", auth),
} }
@@ -141,9 +63,6 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
if !root { if !root {
lines = append(lines, "user nonrootuser") lines = append(lines, "user nonrootuser")
} }
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
}
lines = append(lines, []string{ lines = append(lines, []string{
"<crl-verify>", "<crl-verify>",
"-----BEGIN X509 CRL-----", "-----BEGIN X509 CRL-----",
@@ -161,28 +80,3 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
}...) }...)
return lines return lines
} }
func (p *pia) GetPortForward(client network.Client) (port uint16, err error) {
b, err := p.random.GenerateRandomBytes(32)
if err != nil {
return 0, err
}
clientID := hex.EncodeToString(b)
url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID)
content, status, err := client.GetContent(url) // TODO add ctx
switch {
case err != nil:
return 0, err
case status != http.StatusOK:
return 0, fmt.Errorf("status is %d for %s; does your PIA server support port forwarding?", status, url)
case len(content) == 0:
return 0, fmt.Errorf("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding")
}
body := struct {
Port uint16 `json:"port"`
}{}
if err := json.Unmarshal(content, &body); err != nil {
return 0, fmt.Errorf("port forwarding response: %w", err)
}
return body.Port, nil
}

148
internal/provider/piav3.go Normal file
View File

@@ -0,0 +1,148 @@
package provider
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type piaV3 struct {
servers []models.PIAOldServer
randSource rand.Source
}
func newPrivateInternetAccessV3(servers []models.PIAOldServer, timeNow timeNowFunc) *piaV3 {
return &piaV3{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
func (p *piaV3) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
var port uint16
switch selection.Protocol {
case constants.TCP:
switch selection.EncryptionPreset {
case constants.PIAEncryptionPresetNormal:
port = 502
case constants.PIAEncryptionPresetStrong:
port = 501
}
case constants.UDP:
switch selection.EncryptionPreset {
case constants.PIAEncryptionPresetNormal:
port = 1198
case constants.PIAEncryptionPresetStrong:
port = 1197
}
}
if port == 0 {
return connection, fmt.Errorf("combination of protocol %q and encryption %q does not yield any port number", selection.Protocol, selection.EncryptionPreset)
}
if selection.TargetIP != nil {
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
}
servers := filterPIAOldServers(p.servers, selection.Region)
if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q", selection.Region)
}
var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
return pickRandomConnection(connections, p.randSource), nil
}
func (p *piaV3) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
return buildPIAConf(connection, verbosity, root, cipher, auth, extras)
}
func (p *piaV3) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
b := make([]byte, 32)
n, err := rand.New(p.randSource).Read(b) //nolint:gosec
if err != nil {
pfLogger.Error(err)
return
} else if n != 32 {
pfLogger.Error("only read %d bytes instead of 32", n)
return
}
clientID := hex.EncodeToString(b)
url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID)
response, err := client.Get(url) // TODO add ctx
if err != nil {
pfLogger.Error(err)
return
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
pfLogger.Error(fmt.Errorf("%s for %s; does your PIA server support port forwarding?", response.Status, url))
return
}
b, err = ioutil.ReadAll(response.Body)
if err != nil {
pfLogger.Error(err)
return
} else if len(b) == 0 {
pfLogger.Error(fmt.Errorf("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding"))
return
}
body := struct {
Port uint16 `json:"port"`
}{}
if err := json.Unmarshal(b, &body); err != nil {
pfLogger.Error(fmt.Errorf("port forwarding response: %w", err))
return
}
port := body.Port
filepath := syncState(port)
pfLogger.Info("Writing port to %s", filepath)
if err := fileManager.WriteToFile(
string(filepath), []byte(fmt.Sprintf("%d", port)),
files.Permissions(0666),
); err != nil {
pfLogger.Error(err)
}
if err := fw.SetAllowedPort(ctx, port, string(constants.TUN)); err != nil {
pfLogger.Error(err)
}
<-ctx.Done()
if err := fw.RemoveAllowedPort(ctx, port); err != nil {
pfLogger.Error(err)
}
}
func filterPIAOldServers(servers []models.PIAOldServer, region string) (filtered []models.PIAOldServer) {
if len(region) == 0 {
return servers
}
for _, server := range servers {
if strings.EqualFold(server.Region, region) {
return []models.PIAOldServer{server}
}
}
return nil
}

505
internal/provider/piav4.go Normal file
View File

@@ -0,0 +1,505 @@
package provider
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
gluetunLog "github.com/qdm12/gluetun/internal/logging"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type piaV4 struct {
servers []models.PIAServer
timeNow timeNowFunc
randSource rand.Source
activeServer models.PIAServer
activeProtocol models.NetworkProtocol
}
func newPrivateInternetAccessV4(servers []models.PIAServer, timeNow timeNowFunc) *piaV4 {
return &piaV4{
servers: servers,
timeNow: timeNow,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
func (p *piaV4) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
var port uint16
switch selection.Protocol {
case constants.TCP:
switch selection.EncryptionPreset {
case constants.PIAEncryptionPresetNormal:
port = 502
case constants.PIAEncryptionPresetStrong:
port = 501
}
case constants.UDP:
switch selection.EncryptionPreset {
case constants.PIAEncryptionPresetNormal:
port = 1198
case constants.PIAEncryptionPresetStrong:
port = 1197
}
}
if port == 0 {
return connection, fmt.Errorf("combination of protocol %q and encryption %q does not yield any port number", selection.Protocol, selection.EncryptionPreset)
}
if selection.TargetIP != nil {
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
}
servers := filterPIAServers(p.servers, selection.Region)
if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q", selection.Region)
}
var connections []models.OpenVPNConnection
for _, server := range servers {
IPs := server.OpenvpnUDP.IPs
if selection.Protocol == constants.TCP {
IPs = server.OpenvpnTCP.IPs
}
for _, IP := range IPs {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
connection = pickRandomConnection(connections, p.randSource)
// Reverse lookup server from picked connection
found := false
for _, server := range servers {
IPs := server.OpenvpnUDP.IPs
if selection.Protocol == constants.TCP {
IPs = server.OpenvpnTCP.IPs
}
for _, IP := range IPs {
if connection.IP.Equal(IP) {
p.activeServer = server
found = true
break
}
}
if found {
break
}
}
p.activeProtocol = selection.Protocol
return connection, nil
}
func (p *piaV4) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
return buildPIAConf(connection, verbosity, root, cipher, auth, extras)
}
//nolint:gocognit
func (p *piaV4) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
if !p.activeServer.PortForward {
pfLogger.Error("The server %s does not support port forwarding", p.activeServer.Region)
return
}
if gateway == nil {
pfLogger.Error("aborting because: VPN gateway IP address was not found")
return
}
commonName := p.activeServer.OpenvpnUDP.CN
if p.activeProtocol == constants.TCP {
commonName = p.activeServer.OpenvpnTCP.CN
}
client, err := newPIAv4HTTPClient(commonName)
if err != nil {
pfLogger.Error("aborting because: %s", err)
return
}
defer pfLogger.Warn("loop exited")
data, err := readPIAPortForwardData(fileManager)
if err != nil {
pfLogger.Error(err)
}
dataFound := data.Port > 0
durationToExpiration := data.Expiration.Sub(p.timeNow())
expired := durationToExpiration <= 0
if dataFound {
pfLogger.Info("Found persistent forwarded port data for port %d", data.Port)
if expired {
pfLogger.Warn("Forwarded port data expired on %s, getting another one", data.Expiration.Format(time.RFC1123))
} else {
pfLogger.Info("Forwarded port data expires in %s", gluetunLog.FormatDuration(durationToExpiration))
}
}
if !dataFound || expired {
tryUntilSuccessful(ctx, pfLogger, func() error {
data, err = refreshPIAPortForwardData(client, gateway, fileManager)
return err
})
if ctx.Err() != nil {
return
}
durationToExpiration = data.Expiration.Sub(p.timeNow())
}
pfLogger.Info("Port forwarded is %d expiring in %s", data.Port, gluetunLog.FormatDuration(durationToExpiration))
// First time binding
tryUntilSuccessful(ctx, pfLogger, func() error {
return bindPIAPort(client, gateway, data)
})
if ctx.Err() != nil {
return
}
filepath := syncState(data.Port)
pfLogger.Info("Writing port to %s", filepath)
if err := fileManager.WriteToFile(
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
files.Permissions(0666),
); err != nil {
pfLogger.Error(err)
}
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
pfLogger.Error(err)
}
expiryTimer := time.NewTimer(durationToExpiration)
const keepAlivePeriod = 15 * time.Minute
// Timer behaving as a ticker
keepAliveTimer := time.NewTimer(keepAlivePeriod)
for {
select {
case <-ctx.Done():
removeCtx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := fw.RemoveAllowedPort(removeCtx, data.Port); err != nil {
pfLogger.Error(err)
}
if !keepAliveTimer.Stop() {
<-keepAliveTimer.C
}
if !expiryTimer.Stop() {
<-expiryTimer.C
}
return
case <-keepAliveTimer.C:
if err := bindPIAPort(client, gateway, data); err != nil {
pfLogger.Error(err)
}
keepAliveTimer.Reset(keepAlivePeriod)
case <-expiryTimer.C:
pfLogger.Warn("Forward port has expired on %s, getting another one", data.Expiration.Format(time.RFC1123))
oldPort := data.Port
for {
data, err = refreshPIAPortForwardData(client, gateway, fileManager)
if err != nil {
pfLogger.Error(err)
continue
}
break
}
durationToExpiration := data.Expiration.Sub(p.timeNow())
pfLogger.Info("Port forwarded is %d expiring in %s", data.Port, gluetunLog.FormatDuration(durationToExpiration))
if err := fw.RemoveAllowedPort(ctx, oldPort); err != nil {
pfLogger.Error(err)
}
if err := fw.SetAllowedPort(ctx, data.Port, string(constants.TUN)); err != nil {
pfLogger.Error(err)
}
filepath := syncState(data.Port)
pfLogger.Info("Writing port to %s", filepath)
if err := fileManager.WriteToFile(
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
files.Permissions(0666),
); err != nil {
pfLogger.Error(err)
}
if err := bindPIAPort(client, gateway, data); err != nil {
pfLogger.Error(err)
}
if !keepAliveTimer.Stop() {
<-keepAliveTimer.C
}
keepAliveTimer.Reset(keepAlivePeriod)
expiryTimer.Reset(durationToExpiration)
}
}
}
func filterPIAServers(servers []models.PIAServer, region string) (filtered []models.PIAServer) {
if len(region) == 0 {
return servers
}
for _, server := range servers {
if strings.EqualFold(server.Region, region) {
return []models.PIAServer{server}
}
}
return nil
}
func newPIAv4HTTPClient(serverName string) (client *http.Client, err error) {
certificateBytes, err := base64.StdEncoding.DecodeString(constants.PIACertificateStrong)
if err != nil {
return nil, fmt.Errorf("cannot decode PIA root certificate: %w", err)
}
certificate, err := x509.ParseCertificate(certificateBytes)
if err != nil {
return nil, fmt.Errorf("cannot parse PIA root certificate: %w", err)
}
rootCAs := x509.NewCertPool()
rootCAs.AddCert(certificate)
TLSClientConfig := &tls.Config{
RootCAs: rootCAs,
MinVersion: tls.VersionTLS12,
ServerName: serverName,
}
transport := http.Transport{
TLSClientConfig: TLSClientConfig,
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
const httpTimeout = 5 * time.Second
client = &http.Client{Transport: &transport, Timeout: httpTimeout}
return client, nil
}
func refreshPIAPortForwardData(client *http.Client, gateway net.IP, fileManager files.FileManager) (data piaPortForwardData, err error) {
data.Token, err = fetchPIAToken(fileManager, client)
if err != nil {
return data, fmt.Errorf("cannot obtain token: %w", err)
}
data.Port, data.Signature, data.Expiration, err = fetchPIAPortForwardData(client, gateway, data.Token)
if err != nil {
return data, fmt.Errorf("cannot obtain port forwarding data: %w", err)
}
if err := writePIAPortForwardData(fileManager, data); err != nil {
return data, fmt.Errorf("cannot persist port forwarding information to file: %w", err)
}
return data, nil
}
type piaPayload struct {
Token string `json:"token"`
Port uint16 `json:"port"`
Expiration time.Time `json:"expires_at"`
}
type piaPortForwardData struct {
Port uint16 `json:"port"`
Token string `json:"token"`
Signature string `json:"signature"`
Expiration time.Time `json:"expires_at"`
}
func readPIAPortForwardData(fileManager files.FileManager) (data piaPortForwardData, err error) {
const filepath = string(constants.PIAPortForward)
exists, err := fileManager.FileExists(filepath)
if err != nil {
return data, err
} else if !exists {
return data, nil
}
b, err := fileManager.ReadFile(filepath)
if err != nil {
return data, err
}
if err := json.Unmarshal(b, &data); err != nil {
return data, err
}
return data, nil
}
func writePIAPortForwardData(fileManager files.FileManager, data piaPortForwardData) (err error) {
b, err := json.Marshal(&data)
if err != nil {
return fmt.Errorf("cannot encode data: %w", err)
}
err = fileManager.WriteToFile(string(constants.PIAPortForward), b)
if err != nil {
return err
}
return nil
}
func unpackPIAPayload(payload string) (port uint16, token string, expiration time.Time, err error) {
b, err := base64.RawStdEncoding.DecodeString(payload)
if err != nil {
return 0, "", expiration, fmt.Errorf("cannot decode payload: %w", err)
}
var payloadData piaPayload
if err := json.Unmarshal(b, &payloadData); err != nil {
return 0, "", expiration, fmt.Errorf("cannot parse payload data: %w", err)
}
return payloadData.Port, payloadData.Token, payloadData.Expiration, nil
}
func packPIAPayload(port uint16, token string, expiration time.Time) (payload string, err error) {
payloadData := piaPayload{
Token: token,
Port: port,
Expiration: expiration,
}
b, err := json.Marshal(&payloadData)
if err != nil {
return "", fmt.Errorf("cannot serialize payload data: %w", err)
}
payload = base64.RawStdEncoding.EncodeToString(b)
return payload, nil
}
func fetchPIAToken(fileManager files.FileManager, client *http.Client) (token string, err error) {
username, password, err := getOpenvpnCredentials(fileManager)
if err != nil {
return "", fmt.Errorf("cannot get Openvpn credentials: %w", err)
}
url := url.URL{
Scheme: "https",
User: url.UserPassword(username, password),
Host: "10.0.0.1",
Path: "/authv3/generateToken",
}
request, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil {
return "", err
}
response, err := client.Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
b, err := ioutil.ReadAll(response.Body)
if response.StatusCode != http.StatusOK {
shortenMessage := string(b)
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
return "", fmt.Errorf("%s: response received: %q", response.Status, shortenMessage)
} else if err != nil {
return "", err
}
var result struct {
Token string `json:"token"`
}
if err := json.Unmarshal(b, &result); err != nil {
return "", err
} else if len(result.Token) == 0 {
return "", fmt.Errorf("token is empty")
}
return result.Token, nil
}
func getOpenvpnCredentials(fileManager files.FileManager) (username, password string, err error) {
authData, err := fileManager.ReadFile(string(constants.OpenVPNAuthConf))
if err != nil {
return "", "", fmt.Errorf("cannot read openvpn auth file: %w", err)
}
lines := strings.Split(string(authData), "\n")
if len(lines) < 2 {
return "", "", fmt.Errorf("not enough lines (%d) in openvpn auth file", len(lines))
}
username, password = lines[0], lines[1]
return username, password, nil
}
func fetchPIAPortForwardData(client *http.Client, gateway net.IP, token string) (port uint16, signature string, expiration time.Time, err error) {
queryParams := url.Values{}
queryParams.Add("token", token)
url := url.URL{
Scheme: "https",
Host: net.JoinHostPort(gateway.String(), "19999"),
Path: "/getSignature",
RawQuery: queryParams.Encode(),
}
response, err := client.Get(url.String())
if err != nil {
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %s", response.Status)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
}
var data struct {
Status string `json:"status"`
Payload string `json:"payload"`
Signature string `json:"signature"`
}
if err := json.Unmarshal(b, &data); err != nil {
return 0, "", expiration, fmt.Errorf("cannot decode received data: %w", err)
} else if data.Status != "OK" {
return 0, "", expiration, fmt.Errorf("response received from PIA has status %s", data.Status)
}
port, _, expiration, err = unpackPIAPayload(data.Payload)
return port, data.Signature, expiration, err
}
func bindPIAPort(client *http.Client, gateway net.IP, data piaPortForwardData) (err error) {
payload, err := packPIAPayload(data.Port, data.Token, data.Expiration)
if err != nil {
return err
}
queryParams := url.Values{}
queryParams.Add("payload", payload)
queryParams.Add("signature", data.Signature)
url := url.URL{
Scheme: "https",
Host: net.JoinHostPort(gateway.String(), "19999"),
Path: "/bindPort",
RawQuery: queryParams.Encode(),
}
response, err := client.Get(url.String())
if err != nil {
return fmt.Errorf("cannot bind port: %w", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("cannot bind port: %s", response.Status)
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("cannot bind port: %w", err)
}
var responseData struct {
Status string `json:"status"`
Message string `json:"message"`
}
if err := json.Unmarshal(b, &responseData); err != nil {
return fmt.Errorf("cannot bind port: %w", err)
} else if responseData.Status != "OK" {
return fmt.Errorf("response received from PIA: %s (%s)", responseData.Status, responseData.Message)
}
return nil
}

View File

@@ -1,38 +1,46 @@
package provider package provider
import ( import (
"context"
"net"
"net/http"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
// Provider contains methods to read and modify the openvpn configuration to connect as a client // Provider contains methods to read and modify the openvpn configuration to connect as a client
type Provider interface { type Provider interface {
GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error)
BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
GetPortForward(client network.Client) (port uint16, err error) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath))
} }
func New(provider models.VPNProvider, allServers models.AllServers) Provider { func New(provider models.VPNProvider, allServers models.AllServers, timeNow timeNowFunc) Provider {
switch provider { switch provider {
case constants.PrivateInternetAccess: case constants.PrivateInternetAccess:
return newPrivateInternetAccess(allServers.Pia.Servers) return newPrivateInternetAccessV4(allServers.Pia.Servers, timeNow)
case constants.PrivateInternetAccessOld: case constants.PrivateInternetAccessOld:
return newPrivateInternetAccess(allServers.PiaOld.Servers) return newPrivateInternetAccessV3(allServers.PiaOld.Servers, timeNow)
case constants.Mullvad: case constants.Mullvad:
return newMullvad(allServers.Mullvad.Servers) return newMullvad(allServers.Mullvad.Servers, timeNow)
case constants.Windscribe: case constants.Windscribe:
return newWindscribe(allServers.Windscribe.Servers) return newWindscribe(allServers.Windscribe.Servers, timeNow)
case constants.Surfshark: case constants.Surfshark:
return newSurfshark(allServers.Surfshark.Servers) return newSurfshark(allServers.Surfshark.Servers, timeNow)
case constants.Cyberghost: case constants.Cyberghost:
return newCyberghost(allServers.Cyberghost.Servers) return newCyberghost(allServers.Cyberghost.Servers, timeNow)
case constants.Vyprvpn: case constants.Vyprvpn:
return newVyprvpn(allServers.Vyprvpn.Servers) return newVyprvpn(allServers.Vyprvpn.Servers, timeNow)
case constants.Nordvpn: case constants.Nordvpn:
return newNordvpn(allServers.Nordvpn.Servers) return newNordvpn(allServers.Nordvpn.Servers, timeNow)
case constants.Purevpn: case constants.Purevpn:
return newPurevpn(allServers.Purevpn.Servers) return newPurevpn(allServers.Purevpn.Servers, timeNow)
default: default:
return nil // should never occur return nil // should never occur
} }

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type purevpn struct { type purevpn struct {
servers []models.PurevpnServer servers []models.PurevpnServer
randSource rand.Source
} }
func newPurevpn(servers []models.PurevpnServer) *purevpn { func newPurevpn(servers []models.PurevpnServer, timeNow timeNowFunc) *purevpn {
return &purevpn{ return &purevpn{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -39,12 +47,7 @@ func (p *purevpn) filterServers(region, country, city string) (servers []models.
return servers return servers
} }
func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) (connection 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 var port uint16
switch { switch {
case selection.Protocol == constants.UDP: case selection.Protocol == constants.UDP:
@@ -52,33 +55,29 @@ func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
case selection.Protocol == constants.TCP: case selection.Protocol == constants.TCP:
port = 80 port = 80
default: default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol) return connection, 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 { if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP) return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
} }
if len(connections) > 64 { servers := p.filterServers(selection.Region, selection.Country, selection.City)
connections = connections[:64] if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q, country %q and city %q", selection.Region, selection.Country, selection.City)
} }
return connections, nil var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
return pickRandomConnection(connections, p.randSource), nil
} }
func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -109,15 +108,13 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", string(connections[0].Protocol)), fmt.Sprintf("proto %s", string(connection.Protocol)),
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
} }
if !root { if !root {
lines = append(lines, "user nonrootuser") 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{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -151,12 +148,14 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
if len(auth) > 0 { if len(auth) > 0 {
lines = append(lines, "auth "+auth) lines = append(lines, "auth "+auth)
} }
if connections[0].Protocol == constants.UDP { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") lines = append(lines, "explicit-exit-notify")
} }
return lines return lines
} }
func (p *purevpn) GetPortForward(client network.Client) (port uint16, err error) { func (p *purevpn) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for purevpn") panic("port forwarding is not supported for purevpn")
} }

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type surfshark struct { type surfshark struct {
servers []models.SurfsharkServer servers []models.SurfsharkServer
randSource rand.Source
} }
func newSurfshark(servers []models.SurfsharkServer) *surfshark { func newSurfshark(servers []models.SurfsharkServer, timeNow timeNowFunc) *surfshark {
return &surfshark{ return &surfshark{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -31,12 +39,7 @@ func (s *surfshark) filterServers(region string) (servers []models.SurfsharkServ
return nil return nil
} }
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) (connection 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 var port uint16
switch { switch {
case selection.Protocol == constants.TCP: case selection.Protocol == constants.TCP:
@@ -44,33 +47,33 @@ func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (con
case selection.Protocol == constants.UDP: case selection.Protocol == constants.UDP:
port = 1194 port = 1194
default: default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol) return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
} }
if selection.TargetIP != nil {
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
}
servers := s.filterServers(selection.Region)
if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q", selection.Region)
}
var connections []models.OpenVPNConnection
for _, server := range servers { for _, server := range servers {
for _, IP := range server.IPs { for _, IP := range server.IPs {
if selection.TargetIP != nil { connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
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 { if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP) return connection, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
} }
if len(connections) > 64 { return pickRandomConnection(connections, s.randSource), nil
connections = connections[:64]
}
return connections, nil
} }
func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -107,16 +110,14 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", connections[0].Protocol), fmt.Sprintf("proto %s", connection.Protocol),
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth), fmt.Sprintf("auth %s", auth),
} }
if !root { if !root {
lines = append(lines, "user nonrootuser") lines = append(lines, "user nonrootuser")
} }
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
}
lines = append(lines, []string{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -135,6 +136,8 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
return lines return lines
} }
func (s *surfshark) GetPortForward(client network.Client) (port uint16, err error) { func (s *surfshark) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for surfshark") panic("port forwarding is not supported for surfshark")
} }

View File

@@ -0,0 +1,37 @@
package provider
import (
"context"
"math/rand"
"time"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/logging"
)
type timeNowFunc func() time.Time
func tryUntilSuccessful(ctx context.Context, logger logging.Logger, fn func() error) {
const retryPeriod = 10 * time.Second
for {
err := fn()
if err == nil {
break
}
logger.Error(err)
logger.Info("Trying again in %s", retryPeriod)
timer := time.NewTimer(retryPeriod)
select {
case <-timer.C:
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return
}
}
}
func pickRandomConnection(connections []models.OpenVPNConnection, source rand.Source) models.OpenVPNConnection {
return connections[rand.New(source).Intn(len(connections))] //nolint:gosec
}

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type vyprvpn struct { type vyprvpn struct {
servers []models.VyprvpnServer servers []models.VyprvpnServer
randSource rand.Source
} }
func newVyprvpn(servers []models.VyprvpnServer) *vyprvpn { func newVyprvpn(servers []models.VyprvpnServer, timeNow timeNowFunc) *vyprvpn {
return &vyprvpn{ return &vyprvpn{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -31,46 +39,37 @@ func (v *vyprvpn) filterServers(region string) (servers []models.VyprvpnServer)
return nil return nil
} }
func (v *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection 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 var port uint16
switch { switch {
case selection.Protocol == constants.TCP: case selection.Protocol == constants.TCP:
return nil, fmt.Errorf("TCP protocol not supported by this VPN provider") return connection, fmt.Errorf("TCP protocol not supported by this VPN provider")
case selection.Protocol == constants.UDP: case selection.Protocol == constants.UDP:
port = 443 port = 443
default: default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol) return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
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 { if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP) return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
} }
if len(connections) > 64 { servers := v.filterServers(selection.Region)
connections = connections[:64] if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q", selection.Region)
} }
return connections, nil var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
return pickRandomConnection(connections, v.randSource), nil
} }
func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -101,16 +100,14 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", connections[0].Protocol), fmt.Sprintf("proto %s", connection.Protocol),
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth), fmt.Sprintf("auth %s", auth),
} }
if !root { if !root {
lines = append(lines, "user nonrootuser") lines = append(lines, "user nonrootuser")
} }
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
}
lines = append(lines, []string{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -121,6 +118,8 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
return lines return lines
} }
func (v *vyprvpn) GetPortForward(client network.Client) (port uint16, err error) { func (v *vyprvpn) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for vyprvpn") panic("port forwarding is not supported for vyprvpn")
} }

View File

@@ -1,21 +1,29 @@
package provider package provider
import ( import (
"context"
"fmt" "fmt"
"math/rand"
"net"
"net/http"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
) )
type windscribe struct { type windscribe struct {
servers []models.WindscribeServer servers []models.WindscribeServer
randSource rand.Source
} }
func newWindscribe(servers []models.WindscribeServer) *windscribe { func newWindscribe(servers []models.WindscribeServer, timeNow timeNowFunc) *windscribe {
return &windscribe{ return &windscribe{
servers: servers, servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
} }
} }
@@ -31,12 +39,7 @@ func (w *windscribe) filterServers(region string) (servers []models.WindscribeSe
return nil return nil
} }
func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (connection 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 var port uint16
switch { switch {
case selection.CustomPort > 0: case selection.CustomPort > 0:
@@ -46,33 +49,29 @@ func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (co
case selection.Protocol == constants.UDP: case selection.Protocol == constants.UDP:
port = 443 port = 443
default: default:
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol) return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
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 { if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP) return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
} }
if len(connections) > 64 { servers := w.filterServers(selection.Region)
connections = connections[:64] if len(servers) == 0 {
return connection, fmt.Errorf("no server found for region %q", selection.Region)
} }
return connections, nil var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
return pickRandomConnection(connections, w.randSource), nil
} }
func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { func (w *windscribe) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
if len(cipher) == 0 { if len(cipher) == 0 {
cipher = aes256cbc cipher = aes256cbc
} }
@@ -102,7 +101,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
// Modified variables // Modified variables
fmt.Sprintf("verb %d", verbosity), fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", connections[0].Protocol), fmt.Sprintf("proto %s", connection.Protocol),
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
fmt.Sprintf("cipher %s", cipher), fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth), fmt.Sprintf("auth %s", auth),
} }
@@ -112,9 +112,6 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
if !root { if !root {
lines = append(lines, "user nonrootuser") lines = append(lines, "user nonrootuser")
} }
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
}
lines = append(lines, []string{ lines = append(lines, []string{
"<ca>", "<ca>",
"-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----",
@@ -133,6 +130,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
return lines return lines
} }
func (w *windscribe) GetPortForward(client network.Client) (port uint16, err error) { func (w *windscribe) PortForward(ctx context.Context, client *http.Client,
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath models.Filepath)) {
panic("port forwarding is not supported for windscribe") panic("port forwarding is not supported for windscribe")
} }

View File

@@ -32,6 +32,8 @@ type looper struct {
restart chan struct{} restart chan struct{}
stop chan struct{} stop chan struct{}
updateTicker chan struct{} updateTicker chan struct{}
timeNow func() time.Time
timeSince func(time.Time) time.Duration
} }
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager, func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
@@ -47,6 +49,8 @@ func NewLooper(client network.Client, logger logging.Logger, fileManager files.F
restart: make(chan struct{}), restart: make(chan struct{}),
stop: make(chan struct{}), stop: make(chan struct{}),
updateTicker: make(chan struct{}), updateTicker: make(chan struct{}),
timeNow: time.Now,
timeSince: time.Since,
} }
} }
@@ -127,23 +131,42 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) { func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
ticker := time.NewTicker(time.Hour) timer := time.NewTimer(time.Hour)
timer.Stop() // 1 hour, cannot be a race condition
timerIsStopped := true
period := l.GetPeriod() period := l.GetPeriod()
if period > 0 { if period > 0 {
ticker = time.NewTicker(period) timer.Reset(period)
} else { timerIsStopped = false
ticker.Stop()
} }
lastTick := time.Unix(0, 0)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
ticker.Stop() if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return return
case <-ticker.C: case <-timer.C:
lastTick = l.timeNow()
l.restart <- struct{}{} l.restart <- struct{}{}
timer.Reset(l.GetPeriod())
case <-l.updateTicker: case <-l.updateTicker:
ticker.Stop() if !timer.Stop() {
ticker = time.NewTicker(l.GetPeriod()) <-timer.C
}
timerIsStopped = true
period := l.GetPeriod()
if period == 0 {
continue
}
var waited time.Duration
if lastTick.UnixNano() > 0 {
waited = l.timeSince(lastTick)
}
leftToWait := period - waited
timer.Reset(leftToWait)
timerIsStopped = false
} }
} }
} }

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/files"
) )
func parseRoutingTable(data []byte) (entries []routingEntry, err error) { func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
@@ -23,12 +24,16 @@ func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
return entries, nil return entries, nil
} }
func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) { func getRoutingEntries(fileManager files.FileManager) (entries []routingEntry, err error) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute)) data, err := fileManager.ReadFile(string(constants.NetRoute))
if err != nil { if err != nil {
return "", nil, err return nil, err
} }
entries, err := parseRoutingTable(data) return parseRoutingTable(data)
}
func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) {
entries, err := getRoutingEntries(r.fileManager)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@@ -52,11 +57,7 @@ func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP
} }
func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) { func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute)) entries, err := getRoutingEntries(r.fileManager)
if err != nil {
return defaultSubnet, err
}
entries, err := parseRoutingTable(data)
if err != nil { if err != nil {
return defaultSubnet, err return defaultSubnet, err
} }
@@ -79,11 +80,7 @@ func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
} }
func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) { func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute)) entries, err := getRoutingEntries(r.fileManager)
if err != nil {
return false, fmt.Errorf("cannot check route existence: %w", err)
}
entries, err := parseRoutingTable(data)
if err != nil { if err != nil {
return false, fmt.Errorf("cannot check route existence: %w", err) return false, fmt.Errorf("cannot check route existence: %w", err)
} }
@@ -96,12 +93,8 @@ func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) {
return false, nil return false, nil
} }
func (r *routing) VPNGatewayIP(defaultInterface string) (ip net.IP, err error) { func (r *routing) VPNDestinationIP(defaultInterface string) (ip net.IP, err error) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute)) entries, err := getRoutingEntries(r.fileManager)
if err != nil {
return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err)
}
entries, err := parseRoutingTable(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err) return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err)
} }
@@ -115,6 +108,20 @@ func (r *routing) VPNGatewayIP(defaultInterface string) (ip net.IP, err error) {
return nil, fmt.Errorf("cannot find VPN gateway IP address from ip routes") return nil, fmt.Errorf("cannot find VPN gateway IP address from ip routes")
} }
func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) {
entries, err := getRoutingEntries(r.fileManager)
if err != nil {
return nil, fmt.Errorf("cannot find VPN local gateway IP address: %w", err)
}
for _, entry := range entries {
if entry.iface == string(constants.TUN) &&
entry.destination.Equal(net.IP{0, 0, 0, 0}) {
return entry.gateway, nil
}
}
return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes")
}
func ipIsPrivate(ip net.IP) bool { func ipIsPrivate(ip net.IP) bool {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return true return true

View File

@@ -291,7 +291,7 @@ eth0 0002A8C0 0100000A 0003 0 0 0 00FFFFFF
} }
} }
func Test_VPNGatewayIP(t *testing.T) { func Test_VPNDestinationIP(t *testing.T) {
t.Parallel() t.Parallel()
tests := map[string]struct { tests := map[string]struct {
defaultInterface string defaultInterface string
@@ -334,7 +334,7 @@ eth0 x
filemanager.EXPECT().ReadFile(string(constants.NetRoute)). filemanager.EXPECT().ReadFile(string(constants.NetRoute)).
Return(tc.data, tc.readErr).Times(1) Return(tc.data, tc.readErr).Times(1)
r := &routing{fileManager: filemanager} r := &routing{fileManager: filemanager}
ip, err := r.VPNGatewayIP(tc.defaultInterface) ip, err := r.VPNDestinationIP(tc.defaultInterface)
if tc.err != nil { if tc.err != nil {
require.Error(t, err) require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error()) assert.Equal(t, tc.err.Error(), err.Error())

View File

@@ -14,7 +14,8 @@ type Routing interface {
DeleteRouteVia(ctx context.Context, subnet net.IPNet) (err error) DeleteRouteVia(ctx context.Context, subnet net.IPNet) (err error)
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
LocalSubnet() (defaultSubnet net.IPNet, err error) LocalSubnet() (defaultSubnet net.IPNet, err error)
VPNGatewayIP(defaultInterface string) (ip net.IP, err error) VPNDestinationIP(defaultInterface string) (ip net.IP, err error)
VPNLocalGatewayIP() (ip net.IP, err error)
SetDebug() SetDebug()
} }

View File

@@ -10,7 +10,16 @@ import (
// GetPIASettings obtains PIA settings from environment variables using the params package. // GetPIASettings obtains PIA settings from environment variables using the params package.
func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
settings.Name = constants.PrivateInternetAccess return getPIASettings(paramsReader, constants.PrivateInternetAccess)
}
// GetPIAOldSettings obtains PIA settings for the older PIA servers (pre summer 2020) from environment variables using the params package.
func GetPIAOldSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
return getPIASettings(paramsReader, constants.PrivateInternetAccessOld)
}
func getPIASettings(paramsReader params.Reader, name models.VPNProvider) (settings models.ProviderSettings, err error) {
settings.Name = name
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
if err != nil { if err != nil {
return settings, err return settings, err
@@ -29,30 +38,6 @@ func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSetting
if err != nil { if err != nil {
return settings, err return settings, err
} }
return settings, nil
}
// GetPIAOldSettings obtains PIA settings for the older PIA servers (pre summer 2020) from environment variables using the params package.
func GetPIAOldSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
settings.Name = constants.PrivateInternetAccessOld
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
if err != nil {
return settings, err
}
settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP()
if err != nil {
return settings, err
}
encryptionPreset, err := paramsReader.GetPIAEncryptionPreset()
if err != nil {
return settings, err
}
settings.ServerSelection.EncryptionPreset = encryptionPreset
settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset
settings.ServerSelection.Region, err = paramsReader.GetPIAOldRegion()
if err != nil {
return settings, err
}
settings.PortForwarding.Enabled, err = paramsReader.GetPortForwarding() settings.PortForwarding.Enabled, err = paramsReader.GetPortForwarding()
if err != nil { if err != nil {
return settings, err return settings, err

View File

@@ -36,9 +36,16 @@ func (s *storage) mergeServers(hardcoded, persistent models.AllServers) (merged
} }
merged.Pia = hardcoded.Pia merged.Pia = hardcoded.Pia
if persistent.Pia.Timestamp > hardcoded.Pia.Timestamp { if persistent.Pia.Timestamp > hardcoded.Pia.Timestamp {
s.logger.Info("Using Private Internet Access servers from file (%s more recent)", versionDiff := hardcoded.Pia.Version - persistent.Pia.Version
getUnixTimeDifference(persistent.Pia.Timestamp, hardcoded.Pia.Timestamp)) if versionDiff > 0 {
merged.Pia = persistent.Pia s.logger.Info("Private Internet Access servers from file discarded because they are %d versions behind",
versionDiff)
merged.Pia = hardcoded.Pia
} else {
s.logger.Info("Using Private Internet Access servers from file (%s more recent)",
getUnixTimeDifference(persistent.Pia.Timestamp, hardcoded.Pia.Timestamp))
merged.Pia = persistent.Pia
}
} }
merged.PiaOld = hardcoded.PiaOld merged.PiaOld = hardcoded.PiaOld
if persistent.PiaOld.Timestamp > hardcoded.PiaOld.Timestamp { if persistent.PiaOld.Timestamp > hardcoded.PiaOld.Timestamp {

View File

@@ -30,6 +30,7 @@ func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers
results := make(chan models.CyberghostServer) results := make(chan models.CyberghostServer)
const maxGoroutines = 10 const maxGoroutines = 10
guard := make(chan struct{}, maxGoroutines) guard := make(chan struct{}, maxGoroutines)
defer close(guard)
for groupID, groupName := range groups { for groupID, groupName := range groups {
for countryCode, region := range possibleCountryCodes { for countryCode, region := range possibleCountryCodes {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
@@ -37,9 +38,7 @@ func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers
} }
const domain = "cg-dialup.net" const domain = "cg-dialup.net"
host := fmt.Sprintf("%s-%s.%s", groupID, countryCode, domain) host := fmt.Sprintf("%s-%s.%s", groupID, countryCode, domain)
guard <- struct{}{} go tryCyberghostHostname(ctx, lookupIP, host, groupName, region, results, guard)
go tryCyberghostHostname(ctx, lookupIP, host, groupName, region, results)
<-guard
} }
} }
for i := 0; i < len(groups)*len(possibleCountryCodes); i++ { for i := 0; i < len(groups)*len(possibleCountryCodes); i++ {
@@ -60,7 +59,11 @@ func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers
func tryCyberghostHostname(ctx context.Context, lookupIP lookupIPFunc, func tryCyberghostHostname(ctx context.Context, lookupIP lookupIPFunc,
host, groupName, region string, host, groupName, region string,
results chan<- models.CyberghostServer) { results chan<- models.CyberghostServer, guard chan struct{}) {
guard <- struct{}{}
defer func() {
<-guard
}()
IPs, err := resolveRepeat(ctx, lookupIP, host, 2) IPs, err := resolveRepeat(ctx, lookupIP, host, 2)
if err != nil || len(IPs) == 0 { if err != nil || len(IPs) == 0 {
results <- models.CyberghostServer{} results <- models.CyberghostServer{}

View File

@@ -30,6 +30,8 @@ type looper struct {
restart chan struct{} restart chan struct{}
stop chan struct{} stop chan struct{}
updateTicker chan struct{} updateTicker chan struct{}
timeNow func() time.Time
timeSince func(time.Time) time.Duration
} }
func NewLooper(options Options, period time.Duration, currentServers models.AllServers, func NewLooper(options Options, period time.Duration, currentServers models.AllServers,
@@ -45,6 +47,8 @@ func NewLooper(options Options, period time.Duration, currentServers models.AllS
restart: make(chan struct{}), restart: make(chan struct{}),
stop: make(chan struct{}), stop: make(chan struct{}),
updateTicker: make(chan struct{}), updateTicker: make(chan struct{}),
timeNow: time.Now,
timeSince: time.Since,
} }
} }
@@ -125,23 +129,41 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) { func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
ticker := time.NewTicker(time.Hour) timer := time.NewTimer(time.Hour)
period := l.GetPeriod() timer.Stop()
if period > 0 { timerIsStopped := true
ticker = time.NewTicker(period) if period := l.GetPeriod(); period > 0 {
} else { timerIsStopped = false
ticker.Stop() timer.Reset(period)
} }
lastTick := time.Unix(0, 0)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
ticker.Stop() if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return return
case <-ticker.C: case <-timer.C:
lastTick = l.timeNow()
l.restart <- struct{}{} l.restart <- struct{}{}
timer.Reset(l.GetPeriod())
case <-l.updateTicker: case <-l.updateTicker:
ticker.Stop() if !timerIsStopped && !timer.Stop() {
ticker = time.NewTicker(l.GetPeriod()) <-timer.C
}
timerIsStopped = true
period := l.GetPeriod()
if period == 0 {
continue
}
var waited time.Duration
if lastTick.UnixNano() > 0 {
waited = l.timeSince(lastTick)
}
leftToWait := period - waited
timer.Reset(leftToWait)
timerIsStopped = false
} }
} }
} }

View File

@@ -1,7 +1,6 @@
package updater package updater
import ( import (
"net"
"strings" "strings"
) )
@@ -15,18 +14,6 @@ func extractRemoteLinesFromOpenvpn(content []byte) (remoteLines []string) {
return remoteLines return remoteLines
} }
func extractIPsFromRemoteLines(remoteLines []string) (ips []net.IP) {
for _, remoteLine := range remoteLines {
fields := strings.Fields(remoteLine)
ip := net.ParseIP(fields[1])
if ip == nil { // not an IP address
continue
}
ips = append(ips, ip)
}
return ips
}
func extractHostnamesFromRemoteLines(remoteLines []string) (hostnames []string) { func extractHostnamesFromRemoteLines(remoteLines []string) (hostnames []string) {
for _, remoteLine := range remoteLines { for _, remoteLine := range remoteLines {
fields := strings.Fields(remoteLine) fields := strings.Fields(remoteLine)

View File

@@ -11,40 +11,6 @@ import (
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
) )
func (u *updater) updatePIA() (err error) {
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn-ip-nextgen.zip"
contents, err := fetchAndExtractFiles(zipURL)
if err != nil {
return err
}
servers := make([]models.PIAServer, 0, len(contents))
for fileName, content := range contents {
remoteLines := extractRemoteLinesFromOpenvpn(content)
if len(remoteLines) == 0 {
return fmt.Errorf("cannot find any remote lines in %s", fileName)
}
IPs := extractIPsFromRemoteLines(remoteLines)
if len(IPs) == 0 {
return fmt.Errorf("cannot find any IP addresses in %s", fileName)
}
region := strings.TrimSuffix(fileName, ".ovpn")
server := models.PIAServer{
Region: region,
IPs: uniqueSortedIPs(IPs),
}
servers = append(servers, server)
}
sort.Slice(servers, func(i, j int) bool {
return servers[i].Region < servers[j].Region
})
if u.options.Stdout {
u.println(stringifyPIAServers(servers))
}
u.servers.Pia.Timestamp = u.timeNow().Unix()
u.servers.Pia.Servers = servers
return nil
}
func (u *updater) updatePIAOld(ctx context.Context) (err error) { func (u *updater) updatePIAOld(ctx context.Context) (err error) {
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn.zip" const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn.zip"
contents, err := fetchAndExtractFiles(zipURL) contents, err := fetchAndExtractFiles(zipURL)
@@ -54,8 +20,8 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
const maxGoroutines = 10 const maxGoroutines = 10
guard := make(chan struct{}, maxGoroutines) guard := make(chan struct{}, maxGoroutines)
errors := make(chan error) errors := make(chan error)
serversCh := make(chan models.PIAServer) serversCh := make(chan models.PIAOldServer)
servers := make([]models.PIAServer, 0, len(contents)) servers := make([]models.PIAOldServer, 0, len(contents))
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer func() { defer func() {
@@ -75,10 +41,8 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
return fmt.Errorf("cannot find any hosts in %s", fileName) return fmt.Errorf("cannot find any hosts in %s", fileName)
} }
region := strings.TrimSuffix(fileName, ".ovpn") region := strings.TrimSuffix(fileName, ".ovpn")
guard <- struct{}{}
wg.Add(1) wg.Add(1)
go resolvePIAHostname(ctx, wg, region, hosts, u.lookupIP, errors, serversCh) go resolvePIAv3Hostname(ctx, wg, region, hosts, u.lookupIP, errors, serversCh, guard)
<-guard
} }
for range contents { for range contents {
select { select {
@@ -99,10 +63,14 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
return nil return nil
} }
func resolvePIAHostname(ctx context.Context, wg *sync.WaitGroup, func resolvePIAv3Hostname(ctx context.Context, wg *sync.WaitGroup,
region string, hosts []string, lookupIP lookupIPFunc, region string, hosts []string, lookupIP lookupIPFunc,
errors chan<- error, serversCh chan<- models.PIAServer) { errors chan<- error, serversCh chan<- models.PIAOldServer, guard chan struct{}) {
defer wg.Done() guard <- struct{}{}
defer func() {
<-guard
wg.Done()
}()
var IPs []net.IP //nolint:prealloc var IPs []net.IP //nolint:prealloc
// usually one single host in this case // usually one single host in this case
// so no need to run in goroutines the for loop below // so no need to run in goroutines the for loop below
@@ -115,26 +83,15 @@ func resolvePIAHostname(ctx context.Context, wg *sync.WaitGroup,
} }
IPs = append(IPs, newIPs...) IPs = append(IPs, newIPs...)
} }
serversCh <- models.PIAServer{ serversCh <- models.PIAOldServer{
Region: region, Region: region,
IPs: uniqueSortedIPs(IPs), IPs: uniqueSortedIPs(IPs),
} }
} }
func stringifyPIAServers(servers []models.PIAServer) (s string) { func stringifyPIAOldServers(servers []models.PIAOldServer) (s string) {
s = "func PIAServers() []models.PIAServer {\n" s = "func PIAOldServers() []models.PIAOldServer {\n"
s += " return []models.PIAServer{\n" s += " return []models.PIAOldServer{\n"
for _, server := range servers {
s += " " + server.String() + ",\n"
}
s += " }\n"
s += "}"
return s
}
func stringifyPIAOldServers(servers []models.PIAServer) (s string) {
s = "func PIAOldServers() []models.PIAServer {\n"
s += " return []models.PIAServer{\n"
for _, server := range servers { for _, server := range servers {
s += " " + server.String() + ",\n" s += " " + server.String() + ",\n"
} }

99
internal/updater/piav4.go Normal file
View File

@@ -0,0 +1,99 @@
package updater
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"sort"
"strings"
"github.com/qdm12/gluetun/internal/models"
)
func (u *updater) updatePIA() (err error) {
const url = "https://serverlist.piaservers.net/vpninfo/servers/v4"
response, err := u.httpGet(url)
if err != nil {
return err
}
defer response.Body.Close()
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
} else if response.StatusCode != http.StatusOK {
return fmt.Errorf("%s: %s", response.Status, strings.ReplaceAll(string(b), "\n", ""))
}
// remove key/signature at the bottom
i := bytes.IndexRune(b, '\n')
b = b[:i]
var data struct {
Regions []struct {
Name string `json:"name"`
PortForward bool `json:"port_forward"`
Servers struct {
UDP []struct {
IP net.IP `json:"ip"`
CN string `json:"cn"`
} `json:"ovpnudp"`
TCP []struct {
IP net.IP `json:"ip"`
CN string `json:"cn"`
} `json:"ovpntcp"`
} `json:"servers"`
} `json:"regions"`
}
if err := json.Unmarshal(b, &data); err != nil {
return err
}
servers := make([]models.PIAServer, 0, len(data.Regions))
for _, region := range data.Regions {
server := models.PIAServer{
Region: region.Name,
PortForward: region.PortForward,
}
for _, udpServer := range region.Servers.UDP {
if len(server.OpenvpnUDP.CN) > 0 && server.OpenvpnUDP.CN != udpServer.CN {
return fmt.Errorf("CN is different for UDP for region %q: %q and %q", region.Name, server.OpenvpnUDP.CN, udpServer.CN)
}
if udpServer.IP != nil {
server.OpenvpnUDP.IPs = append(server.OpenvpnUDP.IPs, udpServer.IP)
}
server.OpenvpnUDP.CN = udpServer.CN
}
for _, tcpServer := range region.Servers.TCP {
if len(server.OpenvpnTCP.CN) > 0 && server.OpenvpnTCP.CN != tcpServer.CN {
return fmt.Errorf("CN is different for TCP for region %q: %q and %q", region.Name, server.OpenvpnTCP.CN, tcpServer.CN)
}
if tcpServer.IP != nil {
server.OpenvpnTCP.IPs = append(server.OpenvpnTCP.IPs, tcpServer.IP)
}
server.OpenvpnTCP.CN = tcpServer.CN
}
servers = append(servers, server)
}
sort.Slice(servers, func(i, j int) bool {
return servers[i].Region < servers[j].Region
})
if u.options.Stdout {
u.println(stringifyPIAServers(servers))
}
u.servers.Pia.Timestamp = u.timeNow().Unix()
u.servers.Pia.Servers = servers
return nil
}
func stringifyPIAServers(servers []models.PIAServer) (s string) {
s = "func PIAServers() []models.PIAServer {\n"
s += " return []models.PIAServer{\n"
for _, server := range servers {
s += " " + server.String() + ",\n"
}
s += " }\n"
s += "}"
return s
}

View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/qdm12/gluetun/internal/logging"
) )
// GetMessage returns a message for the user describing if there is a newer version // GetMessage returns a message for the user describing if there is a newer version
@@ -30,35 +32,12 @@ func GetMessage(version, commitShort string, client *http.Client) (message strin
if tagName == version { if tagName == version {
return fmt.Sprintf("You are running the latest release %s", version), nil return fmt.Sprintf("You are running the latest release %s", version), nil
} }
timeSinceRelease := formatDuration(time.Since(releaseTime)) timeSinceRelease := logging.FormatDuration(time.Since(releaseTime))
return fmt.Sprintf("There is a new release %s (%s) created %s ago", return fmt.Sprintf("There is a new release %s (%s) created %s ago",
tagName, name, timeSinceRelease), tagName, name, timeSinceRelease),
nil nil
} }
func formatDuration(duration time.Duration) string {
switch {
case duration < time.Minute:
seconds := int(duration.Round(time.Second).Seconds())
if seconds < 2 {
return fmt.Sprintf("%d second", seconds)
}
return fmt.Sprintf("%d seconds", seconds)
case duration <= time.Hour:
minutes := int(duration.Round(time.Minute).Minutes())
if minutes == 1 {
return "1 minute"
}
return fmt.Sprintf("%d minutes", minutes)
case duration < 48*time.Hour:
hours := int(duration.Truncate(time.Hour).Hours())
return fmt.Sprintf("%d hours", hours)
default:
days := int(duration.Truncate(time.Hour).Hours() / 24)
return fmt.Sprintf("%d days", days)
}
}
func getLatestRelease(client *http.Client) (tagName, name string, time time.Time, err error) { func getLatestRelease(client *http.Client) (tagName, name string, time time.Time, err error) {
releases, err := getGithubReleases(client) releases, err := getGithubReleases(client)
if err != nil { if err != nil {