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 unbound && \
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

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 |
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
| `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_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
| `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 |
| `REGION` | | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
| `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server **for old only** |
| `PORT_FORWARDING_STATUS_FILE` | `/tmp/gluetun/forwarded_port` | Any filepath | Filepath to store the forwarded port number **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 |
- Mullvad
@@ -352,11 +352,11 @@ There are various ways to achieve this, depending on your use case.
## 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=/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=/tmp/gluetun/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.
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.
## HTTP control server

View File

@@ -3,9 +3,11 @@ package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
@@ -188,7 +190,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
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)
// wait for restartOpenvpn
go openvpnLooper.Run(ctx, wg)
@@ -327,9 +329,9 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
logger.Error(line)
}
switch {
case line == "openvpn: Initialization Sequence Completed":
case strings.Contains(line, "Initialization Sequence Completed"):
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("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")
@@ -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{},
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
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()
tickerWg := &sync.WaitGroup{}
// for linters only
@@ -364,18 +367,35 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
tickerWg.Add(2)
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
if portForwardingEnabled {
time.AfterFunc(5*time.Second, startPortForward)
}
defaultInterface, _, err := routing.DefaultRoute()
if err != nil {
logger.Warn(err)
} else {
vpnGatewayIP, err := routing.VPNGatewayIP(defaultInterface)
vpnDestination, err := routing.VPNDestinationIP(defaultInterface)
if err != nil {
logger.Warn(err)
} 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:

View File

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

View File

@@ -15,6 +15,8 @@ const (
OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf"
// OpenVPNConf is the file path to the OpenVPN client configuration file
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 models.Filepath = "/dev/net/tun"
// 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 {
return []models.PIAServer{
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 74, 184}}},
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 170}}},
{Region: "AU Sydney", IPs: []net.IP{{103, 2, 196, 167}}},
{Region: "Algeria", IPs: []net.IP{{45, 133, 91, 210}}},
{Region: "Andorra", IPs: []net.IP{{45, 139, 49, 241}}},
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 82}}},
{Region: "Armenia", IPs: []net.IP{{45, 139, 50, 232}}},
{Region: "Austria", IPs: []net.IP{{156, 146, 60, 14}}},
{Region: "Bahamas", IPs: []net.IP{{45, 132, 143, 206}}},
{Region: "Bangladesh", IPs: []net.IP{{45, 132, 142, 210}}},
{Region: "Belgium", IPs: []net.IP{{5, 253, 205, 147}}},
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 130}}},
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 13}}},
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 81}}},
{Region: "Cambodia", IPs: []net.IP{{188, 215, 235, 103}}},
{Region: "China", IPs: []net.IP{{45, 132, 193, 234}}},
{Region: "Cyprus", IPs: []net.IP{{45, 132, 137, 235}}},
{Region: "Czech Republic", IPs: []net.IP{{212, 102, 39, 194}}},
{Region: "DE Berlin", IPs: []net.IP{{89, 36, 76, 69}}},
{Region: "DE Frankfurt", IPs: []net.IP{{185, 216, 33, 164}}},
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 124}}},
{Region: "Egypt", IPs: []net.IP{{188, 214, 122, 119}}},
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 10}}},
{Region: "France", IPs: []net.IP{{156, 146, 63, 210}}},
{Region: "Georgia", IPs: []net.IP{{45, 132, 138, 236}}},
{Region: "Greenland", IPs: []net.IP{{45, 131, 209, 233}}},
{Region: "Hungary", IPs: []net.IP{{217, 138, 192, 222}}},
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 85}}},
{Region: "India", IPs: []net.IP{{103, 26, 205, 251}}},
{Region: "Iran", IPs: []net.IP{{45, 131, 4, 208}}},
{Region: "Ireland", IPs: []net.IP{{5, 157, 13, 41}}},
{Region: "Isle of Man", IPs: []net.IP{{45, 132, 140, 213}}},
{Region: "Israel", IPs: []net.IP{{185, 77, 248, 10}}},
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 77}}},
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 164}}},
{Region: "Kazakhstan", IPs: []net.IP{{45, 133, 88, 231}}},
{Region: "Liechtenstein", IPs: []net.IP{{45, 139, 48, 236}}},
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 80}}},
{Region: "Macao", IPs: []net.IP{{45, 137, 197, 207}}},
{Region: "Malta", IPs: []net.IP{{45, 137, 198, 235}}},
{Region: "Mexico", IPs: []net.IP{{77, 81, 142, 5}}},
{Region: "Moldova", IPs: []net.IP{{178, 175, 129, 40}}},
{Region: "Monaco", IPs: []net.IP{{45, 137, 199, 237}}},
{Region: "Mongolia", IPs: []net.IP{{45, 139, 51, 211}}},
{Region: "Montenegro", IPs: []net.IP{{45, 131, 208, 206}}},
{Region: "Morocco", IPs: []net.IP{{45, 131, 211, 234}}},
{Region: "Netherlands", IPs: []net.IP{{37, 235, 101, 73}}},
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 70}}},
{Region: "Nigeria", IPs: []net.IP{{45, 137, 196, 208}}},
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 82}}},
{Region: "Panama", IPs: []net.IP{{45, 131, 210, 206}}},
{Region: "Philippines", IPs: []net.IP{{188, 214, 125, 138}}},
{Region: "Poland", IPs: []net.IP{{217, 138, 209, 243}}},
{Region: "Qatar", IPs: []net.IP{{45, 131, 7, 209}}},
{Region: "Romania", IPs: []net.IP{{185, 45, 15, 22}}},
{Region: "Saudi Arabia", IPs: []net.IP{{45, 131, 6, 208}}},
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 248}}},
{Region: "Singapore", IPs: []net.IP{{156, 146, 57, 123}}},
{Region: "South Africa", IPs: []net.IP{{154, 16, 93, 35}}},
{Region: "Spain", IPs: []net.IP{{195, 181, 167, 42}}},
{Region: "Sri Lanka", IPs: []net.IP{{45, 132, 136, 232}}},
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 150}}},
{Region: "Switzerland", IPs: []net.IP{{212, 102, 37, 77}}},
{Region: "Taiwan", IPs: []net.IP{{188, 214, 106, 70}}},
{Region: "Turkey", IPs: []net.IP{{188, 213, 34, 87}}},
{Region: "UK London", IPs: []net.IP{{37, 235, 96, 26}}},
{Region: "UK Manchester", IPs: []net.IP{{193, 239, 84, 60}}},
{Region: "US Atlanta", IPs: []net.IP{{195, 181, 171, 76}}},
{Region: "US California", IPs: []net.IP{{37, 235, 108, 19}}},
{Region: "US Chicago", IPs: []net.IP{{154, 21, 28, 111}}},
{Region: "US Denver", IPs: []net.IP{{70, 39, 126, 143}}},
{Region: "US Florida", IPs: []net.IP{{37, 235, 98, 18}}},
{Region: "US Houston", IPs: []net.IP{{74, 81, 92, 147}}},
{Region: "US New Jersey", IPs: []net.IP{{37, 235, 103, 75}}},
{Region: "US New York", IPs: []net.IP{{156, 146, 55, 213}}},
{Region: "US Seattle", IPs: []net.IP{{156, 146, 48, 14}}},
{Region: "US Silicon Valley", IPs: []net.IP{{154, 21, 212, 228}}},
{Region: "US Texas", IPs: []net.IP{{154, 29, 131, 17}}},
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 5, 172}}},
{Region: "US West", IPs: []net.IP{{193, 37, 254, 239}}},
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 51}}},
{Region: "United Arab Emirates", IPs: []net.IP{{45, 131, 5, 233}}},
{Region: "Venezuela", IPs: []net.IP{{45, 133, 89, 212}}},
{Region: "Vietnam", IPs: []net.IP{{188, 214, 152, 67}}},
{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", 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", 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: "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: "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: "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: "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: "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: "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: "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", 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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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 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 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 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: "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: "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: "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: "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
}
func PIAOldServers() []models.PIAServer {
return []models.PIAServer{
{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}}},
func PIAOldServers() []models.PIAOldServer {
return []models.PIAOldServer{
{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 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: "Argentina", IPs: []net.IP{{190, 106, 134, 100}}},
{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: "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 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 Toronto", IPs: []net.IP{{66, 115, 142, 130}, {172, 98, 92, 66}, {172, 98, 92, 130}}},
{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}, {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: "Czech Republic", IPs: []net.IP{{185, 216, 35, 66}, {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 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: "Czech Republic", IPs: []net.IP{{212, 102, 39, 1}}},
{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, 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: "Estonia", IPs: []net.IP{{77, 247, 111, 98}}},
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 194}}},
{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, 4}, {188, 126, 89, 194}}},
{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: "Hungary", IPs: []net.IP{{185, 128, 26, 24}}},
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 50}, {45, 133, 193, 66}}},
{Region: "India", IPs: []net.IP{{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: "Israel", IPs: []net.IP{{31, 168, 172, 145}}},
{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}}},
{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}, {193, 56, 252, 250}, {193, 56, 252, 251}, {193, 56, 252, 252}}},
{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: "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: "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: "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}}},
@@ -162,33 +173,33 @@ func PIAOldServers() []models.PIAServer {
{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: "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: "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: "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: "Turkey", IPs: []net.IP{{185, 195, 79, 34}, {185, 195, 79, 82}}},
{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 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 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 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, 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 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 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 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 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 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 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 New York City", IPs: []net.IP{{156, 146, 36, 225}, {156, 146, 55, 198}}},
{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 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 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 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 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{{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, 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{{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, 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, 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, 37, 129}, {156, 146, 58, 1}, {156, 146, 58, 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, 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, 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{{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}}},
}
}

View File

@@ -21,13 +21,13 @@ func GetAllServers() (allServers models.AllServers) {
Servers: NordvpnServers(),
},
Pia: models.PiaServers{
Version: 1,
Timestamp: 1599323261,
Version: 2,
Timestamp: 1602531173,
Servers: PIAServers(),
},
PiaOld: models.PiaServers{
PiaOld: models.PiaOldServers{
Version: 1,
Timestamp: 1600458645,
Timestamp: 1602523433,
Servers: PIAOldServers(),
},
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, "4yL2lFcxXd/l1ByxBQ7d3g", digestServerModelVersion(t, models.MullvadServer{}, allServers.Mullvad.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, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.SurfsharkServer{}, allServers.Surfshark.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, "EU4fTzD7jWC9N5kmN5bOEg", digestServersTimestamp(t, allServers.Mullvad.Servers, allServers.Mullvad.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, "CKszzgA7YX5zqxQGiiOL9g", digestServersTimestamp(t, allServers.PiaOld.Servers, allServers.PiaOld.Timestamp))
assert.Equal(t, "beZCOXNWxzrPsUWCyQM99A", digestServersTimestamp(t, allServers.Pia.Servers, allServers.Pia.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, "q28ju2KJqLhrggJTTjXSiw", digestServersTimestamp(t, allServers.Surfshark.Servers, allServers.Surfshark.Timestamp))
assert.Equal(t, "KdIQWi2tYUM4aMXvWfVBEg", digestServersTimestamp(t, allServers.Vyprvpn.Servers, allServers.Vyprvpn.Timestamp))

View File

@@ -2,9 +2,9 @@ package constants
const (
// 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 = "2020-10-10"
AnnouncementExpiration = "2020-11-15"
)
const (

View File

@@ -34,6 +34,8 @@ type looper struct {
start chan struct{}
stop 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,
@@ -49,6 +51,8 @@ func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
start: make(chan struct{}),
stop: 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) {
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()
if settings.UpdatePeriod > 0 {
ticker = time.NewTicker(settings.UpdatePeriod)
} else {
ticker.Stop()
timer.Reset(settings.UpdatePeriod)
timerIsStopped = false
}
lastTick := time.Unix(0, 0)
for {
select {
case <-ctx.Done():
ticker.Stop()
if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return
case <-ticker.C:
case <-timer.C:
lastTick = l.timeNow()
l.restart <- struct{}{}
settings := l.GetSettings()
timer.Reset(settings.UpdatePeriod)
case <-l.updateTicker:
ticker.Stop()
period := l.GetSettings().UpdatePeriod
ticker = time.NewTicker(period)
if !timer.Stop() {
<-timer.C
}
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 {
return fmt.Errorf("cannot enable firewall: %w", err)
}
for _, conn := range c.vpnConnections {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, conn, remove); err != nil {
if c.vpnConnection.IP != nil {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}

View File

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

View File

@@ -7,95 +7,33 @@ import (
"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()
defer c.stateMutex.Unlock()
if !c.enabled {
c.logger.Info("firewall disabled, only updating VPN connections internal list")
c.vpnConnections = make([]models.OpenVPNConnection, len(connections))
copy(c.vpnConnections, connections)
c.logger.Info("firewall disabled, only updating internal VPN connection")
c.vpnConnection = connection
return nil
}
c.logger.Info("setting VPN connections through firewall...")
c.logger.Info("setting VPN connection through firewall...")
connectionsToAdd := findConnectionsToAdd(c.vpnConnections, connections)
connectionsToRemove := findConnectionsToRemove(c.vpnConnections, connections)
if len(connectionsToAdd) == 0 && len(connectionsToRemove) == 0 {
if c.vpnConnection.Equal(connection) {
return nil
}
c.removeConnections(ctx, connectionsToRemove, c.defaultInterface)
if err := c.addConnections(ctx, connectionsToAdd, c.defaultInterface); err != nil {
return fmt.Errorf("cannot set VPN connections through firewall: %w", err)
}
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 {
remove := true
if c.vpnConnection.IP != nil {
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
c.logger.Error("cannot remove outdated VPN connection through firewall: %s", err)
continue
}
c.vpnConnections = removeConnectionFromConnections(c.vpnConnections, conn)
}
}
func (c *configurator) addConnections(ctx context.Context, connections []models.OpenVPNConnection, defaultInterface string) error {
const remove = false
for _, conn := range connections {
if err := c.acceptOutputTrafficToVPN(ctx, defaultInterface, conn, remove); err != nil {
return err
}
c.vpnConnections = append(c.vpnConnections, conn)
c.vpnConnection = models.OpenVPNConnection{}
remove = false
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
return fmt.Errorf("cannot set VPN connection through firewall: %w", err)
}
c.vpnConnection = connection
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 (
"testing"
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_formatDuration(t *testing.T) {
func Test_FormatDuration(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
duration time.Duration
@@ -57,7 +57,7 @@ func Test_formatDuration(t *testing.T) {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
s := formatDuration(testCase.duration)
s := FormatDuration(testCase.duration)
assert.Equal(t, testCase.s, s)
})
}

View File

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

View File

@@ -8,11 +8,32 @@ import (
)
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"`
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))
}

View File

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

View File

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

View File

@@ -2,7 +2,8 @@ package openvpn
import (
"context"
"fmt"
"net"
"net/http"
"sync"
"time"
@@ -10,17 +11,17 @@ import (
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
)
type Looper interface {
Run(ctx context.Context, wg *sync.WaitGroup)
Restart()
PortForward()
PortForward(vpnGatewayIP net.IP)
GetSettings() (settings settings.OpenVPN)
SetSettings(settings settings.OpenVPN)
GetPortForwarded() (portForwarded uint16)
@@ -42,21 +43,22 @@ type looper struct {
// Configurators
conf Configurator
fw firewall.Configurator
routing routing.Routing
// Other objects
logger logging.Logger
client network.Client
logger, pfLogger logging.Logger
client *http.Client
fileManager files.FileManager
streamMerger command.StreamMerger
cancel context.CancelFunc
// Internal channels
restart chan struct{}
portForwardSignals chan struct{}
portForwardSignals chan net.IP
}
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
uid, gid int, allServers models.AllServers,
conf Configurator, fw firewall.Configurator,
logger logging.Logger, client network.Client, fileManager files.FileManager,
conf Configurator, fw firewall.Configurator, routing routing.Routing,
logger logging.Logger, client *http.Client, fileManager files.FileManager,
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
return &looper{
provider: provider,
@@ -66,18 +68,20 @@ func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
allServers: allServers,
conf: conf,
fw: fw,
routing: routing,
logger: logger.WithPrefix("openvpn: "),
pfLogger: logger.WithPrefix("port forwarding: "),
client: client,
fileManager: fileManager,
streamMerger: streamMerger,
cancel: cancel,
restart: make(chan struct{}),
portForwardSignals: make(chan struct{}),
portForwardSignals: make(chan net.IP),
}
}
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) {
l.settingsMutex.RLock()
@@ -109,16 +113,16 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
for ctx.Err() == nil {
settings := l.GetSettings()
l.allServersMutex.RLock()
providerConf := provider.New(l.provider, l.allServers)
providerConf := provider.New(l.provider, l.allServers, time.Now)
l.allServersMutex.RUnlock()
connections, err := providerConf.GetOpenVPNConnections(settings.Provider.ServerSelection)
connection, err := providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
if err != nil {
l.logger.Error(err)
l.cancel()
return
}
lines := providerConf.BuildConf(
connections,
connection,
settings.Verbosity,
l.uid,
l.gid,
@@ -139,7 +143,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
return
}
if err := l.fw.SetVPNConnections(ctx, connections); err != nil {
if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
l.logger.Error(err)
l.cancel()
return
@@ -158,10 +162,12 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
go func(ctx context.Context) {
for {
select {
// TODO have a way to disable pf with a context
case <-ctx.Done():
return
case <-l.portForwardSignals:
l.portForward(ctx, providerConf, l.client)
case gateway := <-l.portForwardSignals:
wg.Add(1)
go l.portForward(ctx, wg, providerConf, l.client, gateway)
}
}
}(openvpnCtx)
@@ -200,43 +206,25 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
<-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()
if !settings.Provider.PortForwarding.Enabled {
return
}
var port uint16
err := fmt.Errorf("")
for err != nil {
if ctx.Err() != nil {
return
}
port, err = providerConf.GetPortForward(client)
if err != nil {
l.logAndWait(ctx, err)
}
}
l.logger.Info("port forwarded is %d", port)
syncState := func(port uint16) (pfFilepath models.Filepath) {
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)
settings := l.GetSettings()
return settings.Provider.PortForwarding.Filepath
}
providerConf.PortForward(ctx,
client, l.fileManager, l.pfLogger,
gateway, l.fw, syncState)
}
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"))
}
// GetTargetIP obtains the IP address to choose from the list of IP addresses
// available for a particular region, from the environment variable
// OPENVPN_TARGET_IP
// GetTargetIP obtains the IP address to override over the list of IP addresses filtered
// from the environment variable OPENVPN_TARGET_IP
func (r *reader) GetTargetIP() (ip net.IP, err error) {
s, err := r.envParams.GetEnv("OPENVPN_TARGET_IP")
if len(s) == 0 {

View File

@@ -1,21 +1,29 @@
package provider
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type cyberghost struct {
servers []models.CyberghostServer
randSource rand.Source
}
func newCyberghost(servers []models.CyberghostServer) *cyberghost {
func newCyberghost(servers []models.CyberghostServer, timeNow timeNowFunc) *cyberghost {
return &cyberghost{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -34,36 +42,27 @@ func (c *cyberghost) filterServers(region, group string) (servers []models.Cyber
return servers
}
func (c *cyberghost) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := c.filterServers(selection.Region, selection.Group)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group)
func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
if selection.TargetIP != nil {
return models.OpenVPNConnection{IP: selection.TargetIP, Port: 443, Protocol: selection.Protocol}, nil
}
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 _, IP := range server.IPs {
if selection.TargetIP != nil {
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 nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
return pickRandomConnection(connections, c.randSource), 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 {
cipher = aes256cbc
}
@@ -97,7 +96,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
// Modified variables
fmt.Sprintf("verb %d", verbosity),
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("auth %s", auth),
}
@@ -107,9 +107,6 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
if !root {
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{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -135,6 +132,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
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")
}

View File

@@ -1,21 +1,29 @@
package provider
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type mullvad struct {
servers []models.MullvadServer
randSource rand.Source
}
func newMullvad(servers []models.MullvadServer) *mullvad {
func newMullvad(servers []models.MullvadServer, timeNow timeNowFunc) *mullvad {
return &mullvad{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -39,45 +47,36 @@ func (m *mullvad) filterServers(country, city, isp string) (servers []models.Mul
return servers
}
func (m *mullvad) GetOpenVPNConnections(selection models.ServerSelection) (connections []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)
}
func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
var defaultPort uint16 = 1194
if selection.Protocol == constants.TCP {
defaultPort = 443
}
for _, server := range servers {
port := defaultPort
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
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
}
} else {
servers := m.filterServers(selection.Country, selection.City, selection.ISP)
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)
}
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})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
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 {
cipher = aes256cbc
}
@@ -108,7 +107,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables
fmt.Sprintf("verb %d", verbosity),
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),
}
if extras.OpenVPNIPv6 {
@@ -120,9 +120,6 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
if !root {
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{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -134,6 +131,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
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")
}

View File

@@ -1,21 +1,29 @@
package provider
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type nordvpn struct {
servers []models.NordvpnServer
randSource rand.Source
}
func newNordvpn(servers []models.NordvpnServer) *nordvpn {
func newNordvpn(servers []models.NordvpnServer, timeNow timeNowFunc) *nordvpn {
return &nordvpn{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -40,12 +48,7 @@ func (n *nordvpn) filterServers(region string, protocol models.NetworkProtocol,
return servers
}
func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []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)
}
func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
var port uint16
switch {
case selection.Protocol == constants.UDP:
@@ -53,31 +56,27 @@ func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
case selection.Protocol == constants.TCP:
port = 443
default:
return nil, 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})
}
return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
}
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 {
connections = connections[:64]
servers := n.filterServers(selection.Region, selection.Protocol, selection.Number)
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 {
cipher = aes256cbc
}
@@ -114,16 +113,14 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables
fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", string(connections[0].Protocol)),
fmt.Sprintf("proto %s", string(connection.Protocol)),
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
fmt.Sprintf("cipher %s", cipher),
fmt.Sprintf("auth %s", auth),
}
if !root {
lines = append(lines, "user nonrootuser")
}
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port))
}
lines = append(lines, []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -142,6 +139,8 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
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")
}

View File

@@ -1,93 +1,14 @@
package provider
import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/crypto/random"
"github.com/qdm12/golibs/network"
)
type pia struct {
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) {
func buildPIAConf(connection models.OpenVPNConnection, verbosity int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
var X509CRL, certificate string
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
if len(cipher) == 0 {
@@ -131,7 +52,8 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
// Modified variables
fmt.Sprintf("verb %d", verbosity),
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("auth %s", auth),
}
@@ -141,9 +63,6 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
if !root {
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{
"<crl-verify>",
"-----BEGIN X509 CRL-----",
@@ -161,28 +80,3 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
}...)
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
import (
"context"
"net"
"net/http"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"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
type Provider interface {
GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error)
BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
GetPortForward(client network.Client) (port uint16, err error)
GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error)
BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
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 {
case constants.PrivateInternetAccess:
return newPrivateInternetAccess(allServers.Pia.Servers)
return newPrivateInternetAccessV4(allServers.Pia.Servers, timeNow)
case constants.PrivateInternetAccessOld:
return newPrivateInternetAccess(allServers.PiaOld.Servers)
return newPrivateInternetAccessV3(allServers.PiaOld.Servers, timeNow)
case constants.Mullvad:
return newMullvad(allServers.Mullvad.Servers)
return newMullvad(allServers.Mullvad.Servers, timeNow)
case constants.Windscribe:
return newWindscribe(allServers.Windscribe.Servers)
return newWindscribe(allServers.Windscribe.Servers, timeNow)
case constants.Surfshark:
return newSurfshark(allServers.Surfshark.Servers)
return newSurfshark(allServers.Surfshark.Servers, timeNow)
case constants.Cyberghost:
return newCyberghost(allServers.Cyberghost.Servers)
return newCyberghost(allServers.Cyberghost.Servers, timeNow)
case constants.Vyprvpn:
return newVyprvpn(allServers.Vyprvpn.Servers)
return newVyprvpn(allServers.Vyprvpn.Servers, timeNow)
case constants.Nordvpn:
return newNordvpn(allServers.Nordvpn.Servers)
return newNordvpn(allServers.Nordvpn.Servers, timeNow)
case constants.Purevpn:
return newPurevpn(allServers.Purevpn.Servers)
return newPurevpn(allServers.Purevpn.Servers, timeNow)
default:
return nil // should never occur
}

View File

@@ -1,21 +1,29 @@
package provider
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type purevpn struct {
servers []models.PurevpnServer
randSource rand.Source
}
func newPurevpn(servers []models.PurevpnServer) *purevpn {
func newPurevpn(servers []models.PurevpnServer, timeNow timeNowFunc) *purevpn {
return &purevpn{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -39,12 +47,7 @@ func (p *purevpn) filterServers(region, country, city string) (servers []models.
return servers
}
func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
servers := p.filterServers(selection.Region, selection.Country, selection.City)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q, country %q and city %q", selection.Region, selection.Country, selection.City)
}
func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
var port uint16
switch {
case selection.Protocol == constants.UDP:
@@ -52,33 +55,29 @@ func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
case selection.Protocol == constants.TCP:
port = 80
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 := p.filterServers(selection.Region, selection.Country, selection.City)
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)
}
var connections []models.OpenVPNConnection
for _, server := range servers {
for _, IP := range server.IPs {
if selection.TargetIP != nil {
if IP.Equal(selection.TargetIP) {
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
}
} else {
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
}
}
}
if selection.TargetIP != nil {
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
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 {
cipher = aes256cbc
}
@@ -109,15 +108,13 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables
fmt.Sprintf("verb %d", verbosity),
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
fmt.Sprintf("proto %s", string(connections[0].Protocol)),
fmt.Sprintf("proto %s", string(connection.Protocol)),
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
fmt.Sprintf("cipher %s", cipher),
}
if !root {
lines = append(lines, "user nonrootuser")
}
for _, connection := range connections {
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port))
}
lines = append(lines, []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -151,12 +148,14 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
if len(auth) > 0 {
lines = append(lines, "auth "+auth)
}
if connections[0].Protocol == constants.UDP {
if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify")
}
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")
}

View File

@@ -1,21 +1,29 @@
package provider
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type surfshark struct {
servers []models.SurfsharkServer
randSource rand.Source
}
func newSurfshark(servers []models.SurfsharkServer) *surfshark {
func newSurfshark(servers []models.SurfsharkServer, timeNow timeNowFunc) *surfshark {
return &surfshark{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -31,12 +39,7 @@ func (s *surfshark) filterServers(region string) (servers []models.SurfsharkServ
return nil
}
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
servers := s.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
var port uint16
switch {
case selection.Protocol == constants.TCP:
@@ -44,33 +47,33 @@ func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (con
case selection.Protocol == constants.UDP:
port = 1194
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 _, 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)
return connection, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
}
if len(connections) > 64 {
connections = connections[:64]
}
return connections, nil
return pickRandomConnection(connections, s.randSource), 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 {
cipher = aes256cbc
}
@@ -107,16 +110,14 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
// Modified variables
fmt.Sprintf("verb %d", verbosity),
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("auth %s", auth),
}
if !root {
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{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -135,6 +136,8 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
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")
}

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
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type vyprvpn struct {
servers []models.VyprvpnServer
randSource rand.Source
}
func newVyprvpn(servers []models.VyprvpnServer) *vyprvpn {
func newVyprvpn(servers []models.VyprvpnServer, timeNow timeNowFunc) *vyprvpn {
return &vyprvpn{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -31,46 +39,37 @@ func (v *vyprvpn) filterServers(region string) (servers []models.VyprvpnServer)
return nil
}
func (v *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := v.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
var port uint16
switch {
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:
port = 443
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 := v.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 _, 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
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 {
cipher = aes256cbc
}
@@ -101,16 +100,14 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
// Modified variables
fmt.Sprintf("verb %d", verbosity),
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("auth %s", auth),
}
if !root {
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{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -121,6 +118,8 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
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")
}

View File

@@ -1,21 +1,29 @@
package provider
import (
"context"
"fmt"
"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/network"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
type windscribe struct {
servers []models.WindscribeServer
randSource rand.Source
}
func newWindscribe(servers []models.WindscribeServer) *windscribe {
func newWindscribe(servers []models.WindscribeServer, timeNow timeNowFunc) *windscribe {
return &windscribe{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
@@ -31,12 +39,7 @@ func (w *windscribe) filterServers(region string) (servers []models.WindscribeSe
return nil
}
func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
servers := w.filterServers(selection.Region)
if len(servers) == 0 {
return nil, fmt.Errorf("no server found for region %q", selection.Region)
}
func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
var port uint16
switch {
case selection.CustomPort > 0:
@@ -46,33 +49,29 @@ func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (co
case selection.Protocol == constants.UDP:
port = 443
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 := w.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 _, 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
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 {
cipher = aes256cbc
}
@@ -102,7 +101,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
// Modified variables
fmt.Sprintf("verb %d", verbosity),
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("auth %s", auth),
}
@@ -112,9 +112,6 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
if !root {
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{
"<ca>",
"-----BEGIN CERTIFICATE-----",
@@ -133,6 +130,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
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")
}

View File

@@ -32,6 +32,8 @@ type looper struct {
restart chan struct{}
stop 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,
@@ -47,6 +49,8 @@ func NewLooper(client network.Client, logger logging.Logger, fileManager files.F
restart: make(chan struct{}),
stop: 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) {
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()
if period > 0 {
ticker = time.NewTicker(period)
} else {
ticker.Stop()
timer.Reset(period)
timerIsStopped = false
}
lastTick := time.Unix(0, 0)
for {
select {
case <-ctx.Done():
ticker.Stop()
if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return
case <-ticker.C:
case <-timer.C:
lastTick = l.timeNow()
l.restart <- struct{}{}
timer.Reset(l.GetPeriod())
case <-l.updateTicker:
ticker.Stop()
ticker = time.NewTicker(l.GetPeriod())
if !timer.Stop() {
<-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"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/files"
)
func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
@@ -23,12 +24,16 @@ func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
return entries, nil
}
func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
func getRoutingEntries(fileManager files.FileManager) (entries []routingEntry, err error) {
data, err := fileManager.ReadFile(string(constants.NetRoute))
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 {
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) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
if err != nil {
return defaultSubnet, err
}
entries, err := parseRoutingTable(data)
entries, err := getRoutingEntries(r.fileManager)
if err != nil {
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) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
if err != nil {
return false, fmt.Errorf("cannot check route existence: %w", err)
}
entries, err := parseRoutingTable(data)
entries, err := getRoutingEntries(r.fileManager)
if err != nil {
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
}
func (r *routing) VPNGatewayIP(defaultInterface string) (ip net.IP, err error) {
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
if err != nil {
return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err)
}
entries, err := parseRoutingTable(data)
func (r *routing) VPNDestinationIP(defaultInterface string) (ip net.IP, err error) {
entries, err := getRoutingEntries(r.fileManager)
if err != nil {
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")
}
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 {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
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()
tests := map[string]struct {
defaultInterface string
@@ -334,7 +334,7 @@ eth0 x
filemanager.EXPECT().ReadFile(string(constants.NetRoute)).
Return(tc.data, tc.readErr).Times(1)
r := &routing{fileManager: filemanager}
ip, err := r.VPNGatewayIP(tc.defaultInterface)
ip, err := r.VPNDestinationIP(tc.defaultInterface)
if tc.err != nil {
require.Error(t, err)
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)
DefaultRoute() (defaultInterface string, defaultGateway net.IP, 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()
}

View File

@@ -10,7 +10,16 @@ import (
// GetPIASettings obtains PIA settings from environment variables using the params package.
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()
if err != nil {
return settings, err
@@ -29,30 +38,6 @@ func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSetting
if err != nil {
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()
if err != nil {
return settings, err

View File

@@ -36,10 +36,17 @@ func (s *storage) mergeServers(hardcoded, persistent models.AllServers) (merged
}
merged.Pia = hardcoded.Pia
if persistent.Pia.Timestamp > hardcoded.Pia.Timestamp {
versionDiff := hardcoded.Pia.Version - persistent.Pia.Version
if versionDiff > 0 {
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
if persistent.PiaOld.Timestamp > hardcoded.PiaOld.Timestamp {
s.logger.Info("Using Private Internet Access older servers from file (%s more recent)",

View File

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

View File

@@ -30,6 +30,8 @@ type looper struct {
restart chan struct{}
stop 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,
@@ -45,6 +47,8 @@ func NewLooper(options Options, period time.Duration, currentServers models.AllS
restart: make(chan struct{}),
stop: 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) {
defer wg.Done()
ticker := time.NewTicker(time.Hour)
period := l.GetPeriod()
if period > 0 {
ticker = time.NewTicker(period)
} else {
ticker.Stop()
timer := time.NewTimer(time.Hour)
timer.Stop()
timerIsStopped := true
if period := l.GetPeriod(); period > 0 {
timerIsStopped = false
timer.Reset(period)
}
lastTick := time.Unix(0, 0)
for {
select {
case <-ctx.Done():
ticker.Stop()
if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return
case <-ticker.C:
case <-timer.C:
lastTick = l.timeNow()
l.restart <- struct{}{}
timer.Reset(l.GetPeriod())
case <-l.updateTicker:
ticker.Stop()
ticker = time.NewTicker(l.GetPeriod())
if !timerIsStopped && !timer.Stop() {
<-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
import (
"net"
"strings"
)
@@ -15,18 +14,6 @@ func extractRemoteLinesFromOpenvpn(content []byte) (remoteLines []string) {
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) {
for _, remoteLine := range remoteLines {
fields := strings.Fields(remoteLine)

View File

@@ -11,40 +11,6 @@ import (
"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) {
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn.zip"
contents, err := fetchAndExtractFiles(zipURL)
@@ -54,8 +20,8 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
const maxGoroutines = 10
guard := make(chan struct{}, maxGoroutines)
errors := make(chan error)
serversCh := make(chan models.PIAServer)
servers := make([]models.PIAServer, 0, len(contents))
serversCh := make(chan models.PIAOldServer)
servers := make([]models.PIAOldServer, 0, len(contents))
ctx, cancel := context.WithCancel(ctx)
wg := &sync.WaitGroup{}
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)
}
region := strings.TrimSuffix(fileName, ".ovpn")
guard <- struct{}{}
wg.Add(1)
go resolvePIAHostname(ctx, wg, region, hosts, u.lookupIP, errors, serversCh)
<-guard
go resolvePIAv3Hostname(ctx, wg, region, hosts, u.lookupIP, errors, serversCh, guard)
}
for range contents {
select {
@@ -99,10 +63,14 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
return nil
}
func resolvePIAHostname(ctx context.Context, wg *sync.WaitGroup,
func resolvePIAv3Hostname(ctx context.Context, wg *sync.WaitGroup,
region string, hosts []string, lookupIP lookupIPFunc,
errors chan<- error, serversCh chan<- models.PIAServer) {
defer wg.Done()
errors chan<- error, serversCh chan<- models.PIAOldServer, guard chan struct{}) {
guard <- struct{}{}
defer func() {
<-guard
wg.Done()
}()
var IPs []net.IP //nolint:prealloc
// usually one single host in this case
// 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...)
}
serversCh <- models.PIAServer{
serversCh <- models.PIAOldServer{
Region: region,
IPs: uniqueSortedIPs(IPs),
}
}
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
}
func stringifyPIAOldServers(servers []models.PIAServer) (s string) {
s = "func PIAOldServers() []models.PIAServer {\n"
s += " return []models.PIAServer{\n"
func stringifyPIAOldServers(servers []models.PIAOldServer) (s string) {
s = "func PIAOldServers() []models.PIAOldServer {\n"
s += " return []models.PIAOldServer{\n"
for _, server := range servers {
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"
"net/http"
"time"
"github.com/qdm12/gluetun/internal/logging"
)
// 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 {
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",
tagName, name, timeSinceRelease),
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) {
releases, err := getGithubReleases(client)
if err != nil {