Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d2ca377df | ||
|
|
98f778c3bb | ||
|
|
9b9ae69404 | ||
|
|
1c747a10c8 | ||
|
|
c4354871f7 | ||
|
|
9f6450502c | ||
|
|
ae7fc5fe96 | ||
|
|
ec157f102b | ||
|
|
fbecbc1c82 |
@@ -113,4 +113,6 @@ RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables i
|
|||||||
deluser tinyproxy && \
|
deluser tinyproxy && \
|
||||||
deluser unbound && \
|
deluser unbound && \
|
||||||
mkdir /gluetun
|
mkdir /gluetun
|
||||||
|
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
||||||
|
ENV GODEBUG=x509ignoreCN=0
|
||||||
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint
|
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -100,7 +100,7 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
|||||||
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
|
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
|
||||||
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
|
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
|
||||||
| `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root |
|
| `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root |
|
||||||
| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN server (or gateway) IP address to use |
|
| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN IP address to use |
|
||||||
| `OPENVPN_CIPHER` | | i.e. `aes-256-gcm` | Specify a custom cipher to use. It will also set `ncp-disable` if using AES GCM for PIA |
|
| `OPENVPN_CIPHER` | | i.e. `aes-256-gcm` | Specify a custom cipher to use. It will also set `ncp-disable` if using AES GCM for PIA |
|
||||||
| `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
|
| `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
|
||||||
| `OPENVPN_IPV6` | `off` | `on`, `off` | Enable tunneling of IPv6 (only for Mullvad) |
|
| `OPENVPN_IPV6` | `off` | `on`, `off` | Enable tunneling of IPv6 (only for Mullvad) |
|
||||||
@@ -115,8 +115,8 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
|||||||
| 🏁 `PASSWORD` | | | Your password |
|
| 🏁 `PASSWORD` | | | Your password |
|
||||||
| `REGION` | | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
|
| `REGION` | | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
|
||||||
| `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
|
| `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
|
||||||
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server **for old only** |
|
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server |
|
||||||
| `PORT_FORWARDING_STATUS_FILE` | `/tmp/gluetun/forwarded_port` | Any filepath | Filepath to store the forwarded port number **for old only** |
|
| `PORT_FORWARDING_STATUS_FILE` | `/tmp/gluetun/forwarded_port` | Any filepath | Filepath to store the forwarded port number |
|
||||||
|
|
||||||
- Mullvad
|
- Mullvad
|
||||||
|
|
||||||
@@ -352,11 +352,11 @@ There are various ways to achieve this, depending on your use case.
|
|||||||
|
|
||||||
## Private Internet Access port forwarding
|
## Private Internet Access port forwarding
|
||||||
|
|
||||||
Note that [not all regions support port forwarding](https://www.privateinternetaccess.com/helpdesk/kb/articles/how-do-i-enable-port-forwarding-on-my-vpn).
|
When `PORT_FORWARDING=on`, a port will be forwarded on the VPN server side and written to the file specified by `PORT_FORWARDING_STATUS_FILE=/tmp/gluetun/forwarded_port`.
|
||||||
|
|
||||||
When `PORT_FORWARDING=on`, a port will be forwarded on the VPN server side and written to the file specified by `PORT_FORWARDING_STATUS_FILE=/forwarded_port`.
|
|
||||||
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
|
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
|
||||||
|
|
||||||
|
For `VPNSP=private internet access` (default), you will keep the same forwarded port for 60 days as long as you bind mount the `/gluetun` directory.
|
||||||
|
|
||||||
You can also use the HTTP control server (see below) to get the port forwarded.
|
You can also use the HTTP control server (see below) to get the port forwarded.
|
||||||
|
|
||||||
## HTTP control server
|
## HTTP control server
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -188,7 +190,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
|
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
|
||||||
|
|
||||||
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid, allServers,
|
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid, allServers,
|
||||||
ovpnConf, firewallConf, logger, client, fileManager, streamMerger, cancel)
|
ovpnConf, firewallConf, routingConf, logger, httpClient, fileManager, streamMerger, cancel)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// wait for restartOpenvpn
|
// wait for restartOpenvpn
|
||||||
go openvpnLooper.Run(ctx, wg)
|
go openvpnLooper.Run(ctx, wg)
|
||||||
@@ -327,9 +329,9 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
|||||||
logger.Error(line)
|
logger.Error(line)
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case line == "openvpn: Initialization Sequence Completed":
|
case strings.Contains(line, "Initialization Sequence Completed"):
|
||||||
signalTunnelReady()
|
signalTunnelReady()
|
||||||
case line == "openvpn: TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)":
|
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"):
|
||||||
logger.Warn("This means that either...")
|
logger.Warn("This means that either...")
|
||||||
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information")
|
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information")
|
||||||
logger.Warn("2. The VPN server crashed, try changing region")
|
logger.Warn("2. The VPN server crashed, try changing region")
|
||||||
@@ -341,10 +343,11 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocognit
|
||||||
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dnsReadyCh <-chan struct{},
|
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dnsReadyCh <-chan struct{},
|
||||||
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
|
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
|
||||||
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
|
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
|
||||||
versionInformation, portForwardingEnabled bool, startPortForward func()) {
|
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
tickerWg := &sync.WaitGroup{}
|
tickerWg := &sync.WaitGroup{}
|
||||||
// for linters only
|
// for linters only
|
||||||
@@ -364,18 +367,35 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
|||||||
tickerWg.Add(2)
|
tickerWg.Add(2)
|
||||||
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
|
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
|
||||||
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
|
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
|
||||||
if portForwardingEnabled {
|
|
||||||
time.AfterFunc(5*time.Second, startPortForward)
|
|
||||||
}
|
|
||||||
defaultInterface, _, err := routing.DefaultRoute()
|
defaultInterface, _, err := routing.DefaultRoute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(err)
|
logger.Warn(err)
|
||||||
} else {
|
} else {
|
||||||
vpnGatewayIP, err := routing.VPNGatewayIP(defaultInterface)
|
vpnDestination, err := routing.VPNDestinationIP(defaultInterface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(err)
|
logger.Warn(err)
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP)
|
logger.Info("VPN routing IP address: %s", vpnDestination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if portForwardingEnabled {
|
||||||
|
// TODO make instantaneous once v3 go out of service
|
||||||
|
const waitDuration = 5 * time.Second
|
||||||
|
timer := time.NewTimer(waitDuration)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case <-timer.C:
|
||||||
|
// vpnGateway required only for PIA v4
|
||||||
|
vpnGateway, err := routing.VPNLocalGatewayIP()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
logger.Info("VPN gateway IP address: %s", vpnGateway)
|
||||||
|
startPortForward(vpnGateway)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case <-dnsReadyCh:
|
case <-dnsReadyCh:
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ func OpenvpnConfig() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers)
|
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
|
||||||
connections, err := providerConf.GetOpenVPNConnections(allSettings.OpenVPN.Provider.ServerSelection)
|
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lines := providerConf.BuildConf(
|
lines := providerConf.BuildConf(
|
||||||
connections,
|
connection,
|
||||||
allSettings.OpenVPN.Verbosity,
|
allSettings.OpenVPN.Verbosity,
|
||||||
allSettings.System.UID,
|
allSettings.System.UID,
|
||||||
allSettings.System.GID,
|
allSettings.System.GID,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ const (
|
|||||||
OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf"
|
OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf"
|
||||||
// OpenVPNConf is the file path to the OpenVPN client configuration file
|
// OpenVPNConf is the file path to the OpenVPN client configuration file
|
||||||
OpenVPNConf models.Filepath = "/etc/openvpn/target.ovpn"
|
OpenVPNConf models.Filepath = "/etc/openvpn/target.ovpn"
|
||||||
|
// PIAPortForward is the file path to the port forwarding JSON information for PIA v4 servers
|
||||||
|
PIAPortForward models.Filepath = "/gluetun/piaportforward.json"
|
||||||
// TunnelDevice is the file path to tun device
|
// TunnelDevice is the file path to tun device
|
||||||
TunnelDevice models.Filepath = "/dev/net/tun"
|
TunnelDevice models.Filepath = "/dev/net/tun"
|
||||||
// NetRoute is the path to the file containing information on the network route
|
// NetRoute is the path to the file containing information on the network route
|
||||||
|
|||||||
@@ -26,90 +26,101 @@ func PIAGeoChoices() (choices []string) {
|
|||||||
|
|
||||||
func PIAServers() []models.PIAServer {
|
func PIAServers() []models.PIAServer {
|
||||||
return []models.PIAServer{
|
return []models.PIAServer{
|
||||||
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 74, 184}}},
|
{Region: "AU Melbourne", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "melbourne405", IPs: []net.IP{{103, 2, 198, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "melbourne405", IPs: []net.IP{{103, 2, 198, 103}}}},
|
||||||
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 170}}},
|
{Region: "AU Perth", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 186}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 188}}}},
|
||||||
{Region: "AU Sydney", IPs: []net.IP{{103, 2, 196, 167}}},
|
{Region: "AU Sydney", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sydney405", IPs: []net.IP{{27, 50, 76, 132}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney405", IPs: []net.IP{{27, 50, 76, 132}}}},
|
||||||
{Region: "Algeria", IPs: []net.IP{{45, 133, 91, 210}}},
|
{Region: "Albania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 131}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 137}}}},
|
||||||
{Region: "Andorra", IPs: []net.IP{{45, 139, 49, 241}}},
|
{Region: "Algeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "algiers402", IPs: []net.IP{{45, 133, 91, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers402", IPs: []net.IP{{45, 133, 91, 227}}}},
|
||||||
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 82}}},
|
{Region: "Andorra", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "andorra401", IPs: []net.IP{{45, 139, 49, 232}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra401", IPs: []net.IP{{45, 139, 49, 238}}}},
|
||||||
{Region: "Armenia", IPs: []net.IP{{45, 139, 50, 232}}},
|
{Region: "Argentina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 92}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 89}}}},
|
||||||
{Region: "Austria", IPs: []net.IP{{156, 146, 60, 14}}},
|
{Region: "Armenia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "armenia402", IPs: []net.IP{{45, 139, 50, 229}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia402", IPs: []net.IP{{45, 139, 50, 213}}}},
|
||||||
{Region: "Bahamas", IPs: []net.IP{{45, 132, 143, 206}}},
|
{Region: "Austria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 104}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 100}}}},
|
||||||
{Region: "Bangladesh", IPs: []net.IP{{45, 132, 142, 210}}},
|
{Region: "Bahamas", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 206}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 229}}}},
|
||||||
{Region: "Belgium", IPs: []net.IP{{5, 253, 205, 147}}},
|
{Region: "Belgium", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "brussels403", IPs: []net.IP{{5, 253, 205, 147}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "brussels403", IPs: []net.IP{{5, 253, 205, 153}}}},
|
||||||
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 130}}},
|
{Region: "Bosnia and Herzegovina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sarajevo401", IPs: []net.IP{{185, 212, 111, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sarajevo401", IPs: []net.IP{{185, 212, 111, 77}}}},
|
||||||
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 13}}},
|
{Region: "Brazil", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saopaolo402", IPs: []net.IP{{188, 241, 177, 56}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saopaolo402", IPs: []net.IP{{188, 241, 177, 51}}}},
|
||||||
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 81}}},
|
{Region: "Bulgaria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sofia401", IPs: []net.IP{{217, 138, 221, 131}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sofia401", IPs: []net.IP{{217, 138, 221, 133}}}},
|
||||||
{Region: "Cambodia", IPs: []net.IP{{188, 215, 235, 103}}},
|
{Region: "CA Montreal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montreal403", IPs: []net.IP{{172, 98, 71, 62}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montreal403", IPs: []net.IP{{172, 98, 71, 59}}}},
|
||||||
{Region: "China", IPs: []net.IP{{45, 132, 193, 234}}},
|
{Region: "CA Ontario", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "ontario402", IPs: []net.IP{{172, 83, 47, 138}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "ontario402", IPs: []net.IP{{172, 83, 47, 196}}}},
|
||||||
{Region: "Cyprus", IPs: []net.IP{{45, 132, 137, 235}}},
|
{Region: "CA Toronto", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 250}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 251}}}},
|
||||||
{Region: "Czech Republic", IPs: []net.IP{{212, 102, 39, 194}}},
|
{Region: "CA Vancouver", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 70}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 18}}}},
|
||||||
{Region: "DE Berlin", IPs: []net.IP{{89, 36, 76, 69}}},
|
{Region: "Cambodia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 105}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 102}}}},
|
||||||
{Region: "DE Frankfurt", IPs: []net.IP{{185, 216, 33, 164}}},
|
{Region: "China", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 212}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 216}}}},
|
||||||
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 124}}},
|
{Region: "Cyprus", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cyprus402", IPs: []net.IP{{45, 132, 137, 220}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cyprus402", IPs: []net.IP{{45, 132, 137, 225}}}},
|
||||||
{Region: "Egypt", IPs: []net.IP{{188, 214, 122, 119}}},
|
{Region: "Czech Republic", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 148}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 149}}}},
|
||||||
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 10}}},
|
{Region: "DE Berlin", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "berlin410", IPs: []net.IP{{89, 36, 76, 153}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "berlin410", IPs: []net.IP{{89, 36, 76, 149}}}},
|
||||||
{Region: "France", IPs: []net.IP{{156, 146, 63, 210}}},
|
{Region: "DE Frankfurt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 96}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 106}}}},
|
||||||
{Region: "Georgia", IPs: []net.IP{{45, 132, 138, 236}}},
|
{Region: "Denmark", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 93}}}},
|
||||||
{Region: "Greenland", IPs: []net.IP{{45, 131, 209, 233}}},
|
{Region: "Egypt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cairo401", IPs: []net.IP{{188, 214, 122, 106}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cairo401", IPs: []net.IP{{188, 214, 122, 104}}}},
|
||||||
{Region: "Hungary", IPs: []net.IP{{217, 138, 192, 222}}},
|
{Region: "Estonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}},
|
||||||
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 85}}},
|
{Region: "Finland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 45}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 45}}}},
|
||||||
{Region: "India", IPs: []net.IP{{103, 26, 205, 251}}},
|
{Region: "France", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "paris402", IPs: []net.IP{{156, 146, 63, 159}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "paris402", IPs: []net.IP{{156, 146, 63, 159}}}},
|
||||||
{Region: "Iran", IPs: []net.IP{{45, 131, 4, 208}}},
|
{Region: "Georgia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "georgia401", IPs: []net.IP{{45, 132, 138, 245}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "georgia401", IPs: []net.IP{{45, 132, 138, 236}}}},
|
||||||
{Region: "Ireland", IPs: []net.IP{{5, 157, 13, 41}}},
|
{Region: "Greece", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 80}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 84}}}},
|
||||||
{Region: "Isle of Man", IPs: []net.IP{{45, 132, 140, 213}}},
|
{Region: "Greenland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 222}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 208}}}},
|
||||||
{Region: "Israel", IPs: []net.IP{{185, 77, 248, 10}}},
|
{Region: "Hong Kong", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "hongkong402", IPs: []net.IP{{86, 107, 104, 234}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "hongkong402", IPs: []net.IP{{86, 107, 104, 240}}}},
|
||||||
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 77}}},
|
{Region: "Hungary", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 121}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 125}}}},
|
||||||
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 164}}},
|
{Region: "Iceland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}},
|
||||||
{Region: "Kazakhstan", IPs: []net.IP{{45, 133, 88, 231}}},
|
{Region: "India", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mumbai405", IPs: []net.IP{{45, 120, 139, 97}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mumbai405", IPs: []net.IP{{45, 120, 139, 97}}}},
|
||||||
{Region: "Liechtenstein", IPs: []net.IP{{45, 139, 48, 236}}},
|
{Region: "Iran", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "iran402", IPs: []net.IP{{45, 131, 4, 219}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "iran402", IPs: []net.IP{{45, 131, 4, 218}}}},
|
||||||
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 80}}},
|
{Region: "Ireland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dublin404", IPs: []net.IP{{193, 56, 252, 28}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dublin404", IPs: []net.IP{{193, 56, 252, 24}}}},
|
||||||
{Region: "Macao", IPs: []net.IP{{45, 137, 197, 207}}},
|
{Region: "Isle of Man", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "douglas401", IPs: []net.IP{{45, 132, 140, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "douglas401", IPs: []net.IP{{45, 132, 140, 244}}}},
|
||||||
{Region: "Malta", IPs: []net.IP{{45, 137, 198, 235}}},
|
{Region: "Israel", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "jerusalem401", IPs: []net.IP{{185, 77, 248, 19}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "jerusalem401", IPs: []net.IP{{185, 77, 248, 17}}}},
|
||||||
{Region: "Mexico", IPs: []net.IP{{77, 81, 142, 5}}},
|
{Region: "Italy", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "milano402", IPs: []net.IP{{156, 146, 41, 20}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "milano402", IPs: []net.IP{{156, 146, 41, 42}}}},
|
||||||
{Region: "Moldova", IPs: []net.IP{{178, 175, 129, 40}}},
|
{Region: "Japan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 135}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 157}}}},
|
||||||
{Region: "Monaco", IPs: []net.IP{{45, 137, 199, 237}}},
|
{Region: "Kazakhstan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 229}}}},
|
||||||
{Region: "Mongolia", IPs: []net.IP{{45, 139, 51, 211}}},
|
{Region: "Latvia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 12}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 12}}}},
|
||||||
{Region: "Montenegro", IPs: []net.IP{{45, 131, 208, 206}}},
|
{Region: "Liechtenstein", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "liechtenstein401", IPs: []net.IP{{45, 139, 48, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "liechtenstein401", IPs: []net.IP{{45, 139, 48, 242}}}},
|
||||||
{Region: "Morocco", IPs: []net.IP{{45, 131, 211, 234}}},
|
{Region: "Lithuania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 163}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 163}}}},
|
||||||
{Region: "Netherlands", IPs: []net.IP{{37, 235, 101, 73}}},
|
{Region: "Luxembourg", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "luxembourg401", IPs: []net.IP{{92, 223, 89, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "luxembourg401", IPs: []net.IP{{92, 223, 89, 78}}}},
|
||||||
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 70}}},
|
{Region: "Macedonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 115}}}},
|
||||||
{Region: "Nigeria", IPs: []net.IP{{45, 137, 196, 208}}},
|
{Region: "Malta", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "malta401", IPs: []net.IP{{45, 137, 198, 238}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "malta401", IPs: []net.IP{{45, 137, 198, 244}}}},
|
||||||
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 82}}},
|
{Region: "Mexico", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 7}}}},
|
||||||
{Region: "Panama", IPs: []net.IP{{45, 131, 210, 206}}},
|
{Region: "Moldova", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 43}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 44}}}},
|
||||||
{Region: "Philippines", IPs: []net.IP{{188, 214, 125, 138}}},
|
{Region: "Monaco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 226}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 218}}}},
|
||||||
{Region: "Poland", IPs: []net.IP{{217, 138, 209, 243}}},
|
{Region: "Montenegro", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montenegro402", IPs: []net.IP{{45, 131, 208, 212}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montenegro402", IPs: []net.IP{{45, 131, 208, 212}}}},
|
||||||
{Region: "Qatar", IPs: []net.IP{{45, 131, 7, 209}}},
|
{Region: "Morocco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 233}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 248}}}},
|
||||||
{Region: "Romania", IPs: []net.IP{{185, 45, 15, 22}}},
|
{Region: "Netherlands", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "amsterdam416", IPs: []net.IP{{212, 102, 35, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "amsterdam416", IPs: []net.IP{{212, 102, 35, 136}}}},
|
||||||
{Region: "Saudi Arabia", IPs: []net.IP{{45, 131, 6, 208}}},
|
{Region: "New Zealand", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 89}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 94}}}},
|
||||||
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 248}}},
|
{Region: "Norway", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "oslo403", IPs: []net.IP{{46, 246, 122, 124}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "oslo403", IPs: []net.IP{{46, 246, 122, 99}}}},
|
||||||
{Region: "Singapore", IPs: []net.IP{{156, 146, 57, 123}}},
|
{Region: "Panama", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "panama401", IPs: []net.IP{{45, 131, 210, 248}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "panama401", IPs: []net.IP{{45, 131, 210, 231}}}},
|
||||||
{Region: "South Africa", IPs: []net.IP{{154, 16, 93, 35}}},
|
{Region: "Philippines", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 142}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 142}}}},
|
||||||
{Region: "Spain", IPs: []net.IP{{195, 181, 167, 42}}},
|
{Region: "Poland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "warsaw402", IPs: []net.IP{{194, 110, 114, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "warsaw402", IPs: []net.IP{{194, 110, 114, 13}}}},
|
||||||
{Region: "Sri Lanka", IPs: []net.IP{{45, 132, 136, 232}}},
|
{Region: "Portugal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lisbon401", IPs: []net.IP{{89, 26, 241, 72}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lisbon401", IPs: []net.IP{{89, 26, 241, 76}}}},
|
||||||
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 150}}},
|
{Region: "Qatar", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "qatar401", IPs: []net.IP{{45, 131, 7, 234}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "qatar401", IPs: []net.IP{{45, 131, 7, 232}}}},
|
||||||
{Region: "Switzerland", IPs: []net.IP{{212, 102, 37, 77}}},
|
{Region: "Romania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 92}}}},
|
||||||
{Region: "Taiwan", IPs: []net.IP{{188, 214, 106, 70}}},
|
{Region: "Saudi Arabia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 238}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 231}}}},
|
||||||
{Region: "Turkey", IPs: []net.IP{{188, 213, 34, 87}}},
|
{Region: "Serbia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}},
|
||||||
{Region: "UK London", IPs: []net.IP{{37, 235, 96, 26}}},
|
{Region: "Singapore", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 210}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 190}}}},
|
||||||
{Region: "UK Manchester", IPs: []net.IP{{193, 239, 84, 60}}},
|
{Region: "Slovakia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bratislava401", IPs: []net.IP{{37, 120, 221, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bratislava401", IPs: []net.IP{{37, 120, 221, 83}}}},
|
||||||
{Region: "US Atlanta", IPs: []net.IP{{195, 181, 171, 76}}},
|
{Region: "South Africa", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 46}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 44}}}},
|
||||||
{Region: "US California", IPs: []net.IP{{37, 235, 108, 19}}},
|
{Region: "Spain", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 33}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 29}}}},
|
||||||
{Region: "US Chicago", IPs: []net.IP{{154, 21, 28, 111}}},
|
{Region: "Sri Lanka", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 224}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 216}}}},
|
||||||
{Region: "US Denver", IPs: []net.IP{{70, 39, 126, 143}}},
|
{Region: "Sweden", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "stockholm404", IPs: []net.IP{{195, 246, 120, 140}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "stockholm404", IPs: []net.IP{{195, 246, 120, 116}}}},
|
||||||
{Region: "US Florida", IPs: []net.IP{{37, 235, 98, 18}}},
|
{Region: "Switzerland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 104}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 84}}}},
|
||||||
{Region: "US Houston", IPs: []net.IP{{74, 81, 92, 147}}},
|
{Region: "Taiwan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 71}}}},
|
||||||
{Region: "US New Jersey", IPs: []net.IP{{37, 235, 103, 75}}},
|
{Region: "Turkey", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 71}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 76}}}},
|
||||||
{Region: "US New York", IPs: []net.IP{{156, 146, 55, 213}}},
|
{Region: "UK London", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london412", IPs: []net.IP{{37, 235, 96, 109}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london412", IPs: []net.IP{{37, 235, 96, 109}}}},
|
||||||
{Region: "US Seattle", IPs: []net.IP{{156, 146, 48, 14}}},
|
{Region: "UK Manchester", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "manchester460", IPs: []net.IP{{37, 120, 159, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "manchester460", IPs: []net.IP{{37, 120, 159, 122}}}},
|
||||||
{Region: "US Silicon Valley", IPs: []net.IP{{154, 21, 212, 228}}},
|
{Region: "UK Southampton", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 223}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 189}}}},
|
||||||
{Region: "US Texas", IPs: []net.IP{{154, 29, 131, 17}}},
|
{Region: "US Atlanta", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "atlanta421", IPs: []net.IP{{154, 21, 21, 77}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "atlanta421", IPs: []net.IP{{154, 21, 21, 70}}}},
|
||||||
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 5, 172}}},
|
{Region: "US California", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{37, 235, 107, 62}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{37, 235, 107, 17}}}},
|
||||||
{Region: "US West", IPs: []net.IP{{193, 37, 254, 239}}},
|
{Region: "US Chicago", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chicago416", IPs: []net.IP{{154, 21, 114, 12}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chicago416", IPs: []net.IP{{154, 21, 114, 12}}}},
|
||||||
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 51}}},
|
{Region: "US Denver", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "denver402", IPs: []net.IP{{70, 39, 126, 157}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "denver402", IPs: []net.IP{{70, 39, 126, 175}}}},
|
||||||
{Region: "United Arab Emirates", IPs: []net.IP{{45, 131, 5, 233}}},
|
{Region: "US East", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newjersey402", IPs: []net.IP{{37, 235, 103, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newjersey402", IPs: []net.IP{{37, 235, 103, 131}}}},
|
||||||
{Region: "Venezuela", IPs: []net.IP{{45, 133, 89, 212}}},
|
{Region: "US Florida", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "miami405", IPs: []net.IP{{37, 235, 98, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "miami405", IPs: []net.IP{{37, 235, 98, 188}}}},
|
||||||
{Region: "Vietnam", IPs: []net.IP{{188, 214, 152, 67}}},
|
{Region: "US Houston", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "houston418", IPs: []net.IP{{205, 251, 154, 205}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "houston418", IPs: []net.IP{{205, 251, 154, 208}}}},
|
||||||
|
{Region: "US Las Vegas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lasvegas402", IPs: []net.IP{{45, 89, 173, 178}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lasvegas402", IPs: []net.IP{{45, 89, 173, 181}}}},
|
||||||
|
{Region: "US New York", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newyork403", IPs: []net.IP{{156, 146, 54, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newyork403", IPs: []net.IP{{156, 146, 54, 63}}}},
|
||||||
|
{Region: "US Seattle", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "seattle417", IPs: []net.IP{{154, 21, 20, 187}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "seattle417", IPs: []net.IP{{154, 21, 20, 169}}}},
|
||||||
|
{Region: "US Silicon Valley", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "siliconvalley401", IPs: []net.IP{{154, 21, 212, 40}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "siliconvalley401", IPs: []net.IP{{154, 21, 212, 14}}}},
|
||||||
|
{Region: "US Texas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dallas401", IPs: []net.IP{{156, 146, 53, 180}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dallas401", IPs: []net.IP{{156, 146, 53, 186}}}},
|
||||||
|
{Region: "US Washington DC", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "washington412", IPs: []net.IP{{23, 105, 168, 143}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "washington412", IPs: []net.IP{{23, 105, 168, 150}}}},
|
||||||
|
{Region: "US West", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 67}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 121}}}},
|
||||||
|
{Region: "Ukraine", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kiev402", IPs: []net.IP{{62, 149, 20, 23}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kiev402", IPs: []net.IP{{62, 149, 20, 22}}}},
|
||||||
|
{Region: "United Arab Emirates", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 146}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 148}}}},
|
||||||
|
{Region: "Venezuela", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "venezuela402", IPs: []net.IP{{45, 133, 89, 217}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "venezuela402", IPs: []net.IP{{45, 133, 89, 217}}}},
|
||||||
|
{Region: "Vietnam", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 70}}}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,39 +133,39 @@ func PIAOldGeoChoices() (choices []string) {
|
|||||||
return choices
|
return choices
|
||||||
}
|
}
|
||||||
|
|
||||||
func PIAOldServers() []models.PIAServer {
|
func PIAOldServers() []models.PIAOldServer {
|
||||||
return []models.PIAServer{
|
return []models.PIAOldServer{
|
||||||
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {27, 50, 82, 133}, {43, 250, 204, 105}, {43, 250, 204, 107}, {43, 250, 204, 109}, {43, 250, 204, 111}, {43, 250, 204, 113}, {43, 250, 204, 115}, {43, 250, 204, 117}, {43, 250, 204, 119}, {43, 250, 204, 123}, {43, 250, 204, 125}}},
|
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {43, 250, 204, 105}, {43, 250, 204, 107}, {43, 250, 204, 109}, {43, 250, 204, 111}, {43, 250, 204, 113}, {43, 250, 204, 115}, {43, 250, 204, 117}, {43, 250, 204, 119}, {43, 250, 204, 123}, {43, 250, 204, 125}}},
|
||||||
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 91}, {43, 250, 205, 93}, {43, 250, 205, 95}}},
|
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 91}, {43, 250, 205, 93}, {43, 250, 205, 95}}},
|
||||||
{Region: "AU Sydney", IPs: []net.IP{{27, 50, 68, 23}, {27, 50, 70, 87}, {27, 50, 77, 251}, {27, 50, 81, 117}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 43}, {118, 127, 60, 51}, {221, 121, 145, 137}, {221, 121, 145, 145}, {221, 121, 145, 147}, {221, 121, 145, 159}, {221, 121, 146, 203}, {221, 121, 148, 221}, {221, 121, 152, 215}}},
|
{Region: "AU Sydney", IPs: []net.IP{{27, 50, 68, 23}, {27, 50, 70, 87}, {27, 50, 77, 251}, {27, 50, 81, 117}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 51}, {221, 121, 145, 135}, {221, 121, 145, 137}, {221, 121, 145, 145}, {221, 121, 145, 147}, {221, 121, 145, 159}, {221, 121, 146, 203}, {221, 121, 148, 221}, {221, 121, 152, 215}}},
|
||||||
{Region: "Albania", IPs: []net.IP{{31, 171, 154, 114}}},
|
{Region: "Albania", IPs: []net.IP{{31, 171, 154, 114}}},
|
||||||
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 100}}},
|
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 100}}},
|
||||||
{Region: "Austria", IPs: []net.IP{{89, 187, 168, 6}, {156, 146, 60, 129}}},
|
{Region: "Austria", IPs: []net.IP{{89, 187, 168, 6}, {156, 146, 60, 129}}},
|
||||||
{Region: "Belgium", IPs: []net.IP{{77, 243, 191, 18}, {77, 243, 191, 19}, {77, 243, 191, 20}, {77, 243, 191, 21}, {77, 243, 191, 22}, {77, 243, 191, 23}, {185, 104, 186, 26}, {185, 232, 21, 26}, {185, 232, 21, 27}, {185, 232, 21, 28}, {185, 232, 21, 29}}},
|
{Region: "Belgium", IPs: []net.IP{{77, 243, 191, 18}, {77, 243, 191, 19}, {77, 243, 191, 20}, {185, 232, 21, 26}}},
|
||||||
{Region: "Bosnia and Herzegovina", IPs: []net.IP{{185, 164, 35, 54}}},
|
{Region: "Bosnia and Herzegovina", IPs: []net.IP{{185, 164, 35, 54}}},
|
||||||
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 66}}},
|
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 66}}},
|
||||||
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 194}, {199, 36, 223, 130}, {199, 36, 223, 194}}},
|
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 194}, {199, 36, 223, 130}, {199, 36, 223, 194}}},
|
||||||
{Region: "CA Ontario", IPs: []net.IP{{162, 219, 176, 42}, {162, 219, 176, 130}, {162, 219, 176, 194}, {184, 75, 208, 2}, {184, 75, 208, 18}, {184, 75, 208, 34}, {184, 75, 208, 66}, {184, 75, 208, 90}, {184, 75, 208, 114}, {184, 75, 208, 122}, {184, 75, 208, 130}, {184, 75, 208, 146}, {184, 75, 208, 170}, {184, 75, 210, 18}, {184, 75, 210, 194}, {184, 75, 214, 18}, {184, 75, 215, 18}, {184, 75, 215, 26}, {184, 75, 215, 66}, {184, 75, 215, 74}}},
|
{Region: "CA Ontario", IPs: []net.IP{{162, 219, 176, 26}, {162, 219, 176, 42}, {184, 75, 208, 2}, {184, 75, 208, 90}, {184, 75, 208, 114}, {184, 75, 208, 122}, {184, 75, 208, 130}, {184, 75, 208, 146}, {184, 75, 208, 170}, {184, 75, 208, 202}, {184, 75, 210, 18}, {184, 75, 210, 98}, {184, 75, 210, 106}, {184, 75, 213, 186}, {184, 75, 213, 218}, {184, 75, 214, 18}, {184, 75, 215, 18}, {184, 75, 215, 26}, {184, 75, 215, 66}, {184, 75, 215, 74}}},
|
||||||
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 130}, {172, 98, 92, 66}, {172, 98, 92, 130}}},
|
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 130}, {66, 115, 145, 199}, {172, 98, 92, 66}, {172, 98, 92, 130}, {172, 98, 92, 194}}},
|
||||||
{Region: "CA Vancouver", IPs: []net.IP{{162, 216, 47, 66}, {162, 216, 47, 194}, {172, 98, 89, 130}, {172, 98, 89, 194}}},
|
{Region: "CA Vancouver", IPs: []net.IP{{162, 216, 47, 66}, {162, 216, 47, 194}, {172, 98, 89, 130}, {172, 98, 89, 194}}},
|
||||||
{Region: "Czech Republic", IPs: []net.IP{{185, 216, 35, 66}, {212, 102, 39, 1}}},
|
{Region: "Czech Republic", IPs: []net.IP{{212, 102, 39, 1}}},
|
||||||
{Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 230}, {185, 230, 127, 231}, {185, 230, 127, 235}, {185, 230, 127, 236}, {185, 230, 127, 237}, {185, 230, 127, 238}, {185, 230, 127, 239}, {185, 230, 127, 241}, {193, 176, 86, 122}, {193, 176, 86, 124}, {193, 176, 86, 130}, {193, 176, 86, 134}, {193, 176, 86, 142}, {193, 176, 86, 150}, {193, 176, 86, 154}, {193, 176, 86, 166}, {193, 176, 86, 170}, {193, 176, 86, 174}, {193, 176, 86, 178}, {194, 36, 108, 6}}},
|
{Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 238}, {193, 176, 86, 122}, {193, 176, 86, 123}, {193, 176, 86, 134}, {193, 176, 86, 178}, {194, 36, 108, 6}}},
|
||||||
{Region: "DE Frankfurt", IPs: []net.IP{{195, 181, 170, 225}, {195, 181, 170, 239}, {195, 181, 170, 240}, {195, 181, 170, 241}, {195, 181, 170, 242}, {212, 102, 57, 138}}},
|
{Region: "DE Frankfurt", IPs: []net.IP{{195, 181, 170, 239}, {195, 181, 170, 240}, {195, 181, 170, 241}, {195, 181, 170, 242}, {195, 181, 170, 243}, {195, 181, 170, 244}, {212, 102, 57, 138}}},
|
||||||
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 34}}},
|
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 34}}},
|
||||||
{Region: "Estonia", IPs: []net.IP{{77, 247, 111, 98}}},
|
{Region: "Estonia", IPs: []net.IP{{77, 247, 111, 82}, {77, 247, 111, 98}, {77, 247, 111, 114}, {77, 247, 111, 130}}},
|
||||||
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 194}}},
|
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 4}, {188, 126, 89, 194}}},
|
||||||
{Region: "France", IPs: []net.IP{{156, 146, 63, 1}, {156, 146, 63, 65}}},
|
{Region: "France", IPs: []net.IP{{156, 146, 63, 1}, {156, 146, 63, 65}}},
|
||||||
{Region: "Greece", IPs: []net.IP{{154, 57, 3, 91}, {154, 57, 3, 106}, {154, 57, 3, 145}}},
|
{Region: "Greece", IPs: []net.IP{{154, 57, 3, 91}, {154, 57, 3, 106}, {154, 57, 3, 145}}},
|
||||||
{Region: "Hungary", IPs: []net.IP{{185, 128, 26, 24}}},
|
{Region: "Hungary", IPs: []net.IP{{185, 128, 26, 18}, {185, 128, 26, 19}, {185, 128, 26, 20}, {185, 128, 26, 21}, {185, 128, 26, 22}, {185, 128, 26, 23}, {185, 128, 26, 24}, {185, 189, 114, 98}}},
|
||||||
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 50}, {45, 133, 193, 66}}},
|
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 50}}},
|
||||||
{Region: "India", IPs: []net.IP{{150, 242, 12, 155}, {150, 242, 12, 171}, {150, 242, 12, 187}}},
|
{Region: "India", IPs: []net.IP{{45, 120, 139, 108}, {45, 120, 139, 109}, {150, 242, 12, 155}, {150, 242, 12, 171}, {150, 242, 12, 187}}},
|
||||||
{Region: "Ireland", IPs: []net.IP{{193, 56, 252, 210}, {193, 56, 252, 226}, {193, 56, 252, 242}}},
|
{Region: "Ireland", IPs: []net.IP{{193, 56, 252, 210}, {193, 56, 252, 226}, {193, 56, 252, 242}, {193, 56, 252, 250}, {193, 56, 252, 251}, {193, 56, 252, 252}}},
|
||||||
{Region: "Israel", IPs: []net.IP{{31, 168, 172, 145}}},
|
{Region: "Israel", IPs: []net.IP{{31, 168, 172, 142}, {31, 168, 172, 143}, {31, 168, 172, 145}, {31, 168, 172, 146}}},
|
||||||
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 129}, {156, 146, 41, 193}}},
|
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 129}, {156, 146, 41, 193}}},
|
||||||
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 1}, {156, 146, 34, 65}}},
|
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 1}, {156, 146, 34, 65}}},
|
||||||
{Region: "Latvia", IPs: []net.IP{{46, 183, 217, 34}, {46, 183, 218, 130}, {46, 183, 218, 146}}},
|
{Region: "Latvia", IPs: []net.IP{{46, 183, 217, 34}, {46, 183, 218, 130}, {46, 183, 218, 146}}},
|
||||||
{Region: "Lithuania", IPs: []net.IP{{85, 206, 165, 96}, {85, 206, 165, 112}, {85, 206, 165, 128}}},
|
{Region: "Lithuania", IPs: []net.IP{{85, 206, 165, 96}, {85, 206, 165, 112}, {85, 206, 165, 128}}},
|
||||||
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 134}, {92, 223, 89, 135}, {92, 223, 89, 136}, {92, 223, 89, 137}, {92, 223, 89, 138}, {92, 223, 89, 140}}},
|
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 133}, {92, 223, 89, 134}, {92, 223, 89, 135}, {92, 223, 89, 136}, {92, 223, 89, 137}, {92, 223, 89, 138}, {92, 223, 89, 140}, {92, 223, 89, 142}}},
|
||||||
{Region: "Moldova", IPs: []net.IP{{178, 17, 172, 242}, {178, 17, 173, 194}, {178, 175, 128, 34}}},
|
{Region: "Moldova", IPs: []net.IP{{178, 17, 172, 242}, {178, 17, 173, 194}, {178, 175, 128, 34}}},
|
||||||
{Region: "Netherlands", IPs: []net.IP{{89, 187, 174, 198}, {212, 102, 35, 101}, {212, 102, 35, 102}, {212, 102, 35, 103}, {212, 102, 35, 104}}},
|
{Region: "Netherlands", IPs: []net.IP{{89, 187, 174, 198}, {212, 102, 35, 101}, {212, 102, 35, 102}, {212, 102, 35, 103}, {212, 102, 35, 104}}},
|
||||||
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 1}, {43, 250, 207, 3}}},
|
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 1}, {43, 250, 207, 3}}},
|
||||||
@@ -162,33 +173,33 @@ func PIAOldServers() []models.PIAServer {
|
|||||||
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 34}, {46, 246, 122, 162}}},
|
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 34}, {46, 246, 122, 162}}},
|
||||||
{Region: "Poland", IPs: []net.IP{{185, 244, 214, 195}, {185, 244, 214, 196}, {185, 244, 214, 197}, {185, 244, 214, 198}, {185, 244, 214, 199}, {185, 244, 214, 200}}},
|
{Region: "Poland", IPs: []net.IP{{185, 244, 214, 195}, {185, 244, 214, 196}, {185, 244, 214, 197}, {185, 244, 214, 198}, {185, 244, 214, 199}, {185, 244, 214, 200}}},
|
||||||
{Region: "Portugal", IPs: []net.IP{{89, 26, 241, 86}, {89, 26, 241, 102}, {89, 26, 241, 130}}},
|
{Region: "Portugal", IPs: []net.IP{{89, 26, 241, 86}, {89, 26, 241, 102}, {89, 26, 241, 130}}},
|
||||||
{Region: "Romania", IPs: []net.IP{{86, 105, 25, 69}, {86, 105, 25, 70}, {86, 105, 25, 74}, {86, 105, 25, 75}, {86, 105, 25, 77}, {86, 105, 25, 78}, {89, 33, 8, 38}, {89, 33, 8, 42}, {93, 115, 7, 70}, {94, 176, 148, 34}, {143, 244, 54, 1}, {185, 45, 12, 126}, {185, 210, 218, 98}, {185, 210, 218, 99}, {185, 210, 218, 100}, {185, 210, 218, 101}, {185, 210, 218, 104}, {185, 210, 218, 105}, {185, 210, 218, 108}, {188, 240, 220, 26}}},
|
{Region: "Romania", IPs: []net.IP{{86, 105, 25, 69}, {86, 105, 25, 70}, {86, 105, 25, 74}, {86, 105, 25, 75}, {86, 105, 25, 76}, {86, 105, 25, 77}, {86, 105, 25, 78}, {89, 33, 8, 38}, {89, 33, 8, 42}, {93, 115, 7, 70}, {94, 176, 148, 35}, {143, 244, 54, 1}, {185, 45, 12, 126}, {185, 210, 218, 98}, {185, 210, 218, 99}, {185, 210, 218, 100}, {185, 210, 218, 101}, {185, 210, 218, 102}, {185, 210, 218, 105}, {188, 240, 220, 26}}},
|
||||||
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 226}}},
|
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 226}}},
|
||||||
{Region: "Singapore", IPs: []net.IP{{156, 146, 56, 193}, {156, 146, 57, 38}, {156, 146, 57, 235}, {156, 146, 57, 244}}},
|
{Region: "Singapore", IPs: []net.IP{{156, 146, 56, 193}, {156, 146, 57, 38}, {156, 146, 57, 235}, {156, 146, 57, 244}}},
|
||||||
{Region: "Slovakia", IPs: []net.IP{{37, 120, 221, 98}}},
|
{Region: "Slovakia", IPs: []net.IP{{37, 120, 221, 82}, {37, 120, 221, 98}}},
|
||||||
{Region: "South Africa", IPs: []net.IP{{102, 165, 20, 133}}},
|
{Region: "South Africa", IPs: []net.IP{{102, 165, 20, 133}}},
|
||||||
{Region: "Spain", IPs: []net.IP{{212, 102, 49, 185}, {212, 102, 49, 251}}},
|
{Region: "Spain", IPs: []net.IP{{212, 102, 49, 185}, {212, 102, 49, 251}}},
|
||||||
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 253}, {46, 246, 3, 254}}},
|
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 254}}},
|
||||||
{Region: "Switzerland", IPs: []net.IP{{156, 146, 62, 193}, {212, 102, 36, 1}, {212, 102, 36, 166}, {212, 102, 37, 240}, {212, 102, 37, 241}, {212, 102, 37, 242}, {212, 102, 37, 243}}},
|
{Region: "Switzerland", IPs: []net.IP{{156, 146, 62, 193}, {212, 102, 36, 1}, {212, 102, 36, 166}, {212, 102, 37, 240}, {212, 102, 37, 241}, {212, 102, 37, 242}, {212, 102, 37, 243}}},
|
||||||
{Region: "Turkey", IPs: []net.IP{{185, 195, 79, 34}, {185, 195, 79, 82}}},
|
{Region: "Turkey", IPs: []net.IP{{185, 195, 79, 34}, {185, 195, 79, 82}}},
|
||||||
{Region: "UAE", IPs: []net.IP{{45, 9, 250, 46}}},
|
{Region: "UAE", IPs: []net.IP{{45, 9, 250, 46}}},
|
||||||
{Region: "UK London", IPs: []net.IP{{37, 235, 96, 198}, {37, 235, 97, 11}, {212, 102, 52, 1}, {212, 102, 52, 134}, {212, 102, 52, 199}, {212, 102, 53, 93}, {212, 102, 53, 129}}},
|
{Region: "UK London", IPs: []net.IP{{212, 102, 52, 1}}},
|
||||||
{Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 38}, {89, 238, 137, 39}, {89, 238, 139, 52}, {89, 238, 139, 53}, {89, 238, 139, 54}, {89, 238, 139, 55}, {89, 238, 139, 56}, {89, 238, 139, 57}, {89, 238, 139, 58}, {89, 249, 67, 220}}},
|
{Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 38}, {89, 238, 137, 39}, {89, 238, 139, 52}, {89, 238, 139, 53}, {89, 238, 139, 54}, {89, 238, 139, 55}, {89, 238, 139, 56}, {89, 238, 139, 57}, {89, 238, 139, 58}, {89, 249, 67, 220}}},
|
||||||
{Region: "UK Southampton", IPs: []net.IP{{143, 244, 36, 58}, {143, 244, 37, 1}, {143, 244, 38, 1}, {143, 244, 38, 60}, {143, 244, 38, 119}}},
|
{Region: "UK Southampton", IPs: []net.IP{{143, 244, 36, 58}, {143, 244, 37, 1}, {143, 244, 38, 1}, {143, 244, 38, 60}, {143, 244, 38, 119}}},
|
||||||
{Region: "US Atlanta", IPs: []net.IP{{66, 115, 169, 195}, {66, 115, 169, 196}, {66, 115, 169, 197}, {66, 115, 169, 201}, {66, 115, 169, 202}, {66, 115, 169, 204}, {66, 115, 169, 205}, {66, 115, 169, 206}, {66, 115, 169, 209}, {66, 115, 169, 211}, {66, 115, 169, 212}, {66, 115, 169, 213}, {66, 115, 169, 214}, {156, 146, 46, 1}, {156, 146, 46, 134}, {156, 146, 46, 198}, {156, 146, 47, 11}}},
|
{Region: "US Atlanta", IPs: []net.IP{{156, 146, 46, 1}, {156, 146, 46, 134}, {156, 146, 46, 198}, {156, 146, 47, 11}}},
|
||||||
{Region: "US California", IPs: []net.IP{{37, 235, 108, 144}, {89, 187, 187, 129}, {91, 207, 175, 194}, {91, 207, 175, 195}, {91, 207, 175, 196}, {91, 207, 175, 197}, {91, 207, 175, 198}, {91, 207, 175, 200}, {91, 207, 175, 201}, {91, 207, 175, 202}, {91, 207, 175, 203}, {91, 207, 175, 204}, {91, 207, 175, 206}, {91, 207, 175, 207}, {91, 207, 175, 209}, {91, 207, 175, 211}, {91, 207, 175, 212}}},
|
{Region: "US California", IPs: []net.IP{{37, 235, 108, 208}, {89, 187, 187, 129}, {89, 187, 187, 162}, {91, 207, 175, 194}, {91, 207, 175, 195}, {91, 207, 175, 197}, {91, 207, 175, 198}, {91, 207, 175, 199}, {91, 207, 175, 200}, {91, 207, 175, 205}, {91, 207, 175, 206}, {91, 207, 175, 207}, {91, 207, 175, 209}, {91, 207, 175, 210}, {91, 207, 175, 212}}},
|
||||||
{Region: "US Chicago", IPs: []net.IP{{156, 146, 50, 1}, {156, 146, 50, 65}, {156, 146, 50, 134}, {156, 146, 50, 198}, {156, 146, 51, 11}, {212, 102, 58, 113}, {212, 102, 59, 54}, {212, 102, 59, 129}}},
|
{Region: "US Chicago", IPs: []net.IP{{156, 146, 50, 1}, {156, 146, 50, 65}, {156, 146, 50, 134}, {156, 146, 50, 198}, {156, 146, 51, 11}, {212, 102, 58, 113}, {212, 102, 59, 54}, {212, 102, 59, 129}}},
|
||||||
{Region: "US Dallas", IPs: []net.IP{{156, 146, 38, 65}, {156, 146, 38, 161}, {156, 146, 39, 1}, {156, 146, 39, 6}, {156, 146, 52, 6}, {156, 146, 52, 70}, {156, 146, 52, 139}, {156, 146, 52, 203}, {174, 127, 114, 53}, {174, 127, 114, 60}, {174, 127, 114, 68}, {174, 127, 114, 72}, {174, 127, 114, 75}, {174, 127, 114, 77}}},
|
{Region: "US Dallas", IPs: []net.IP{{156, 146, 38, 65}, {156, 146, 38, 161}, {156, 146, 39, 1}, {156, 146, 39, 6}, {156, 146, 52, 6}, {156, 146, 52, 70}, {156, 146, 52, 139}, {156, 146, 52, 203}}},
|
||||||
{Region: "US Denver", IPs: []net.IP{{174, 128, 225, 2}, {174, 128, 225, 98}, {174, 128, 226, 18}, {174, 128, 227, 226}, {174, 128, 236, 98}, {174, 128, 236, 106}, {174, 128, 242, 234}, {174, 128, 242, 250}, {174, 128, 243, 106}, {174, 128, 244, 66}, {174, 128, 245, 98}, {174, 128, 246, 10}, {174, 128, 250, 26}, {199, 115, 97, 202}, {199, 115, 98, 146}, {199, 115, 98, 226}, {199, 115, 98, 234}, {199, 115, 101, 178}, {199, 115, 102, 146}, {199, 115, 103, 10}}},
|
{Region: "US Denver", IPs: []net.IP{{70, 39, 77, 130}, {70, 39, 92, 2}, {70, 39, 113, 194}, {174, 128, 225, 2}, {174, 128, 226, 10}, {174, 128, 226, 18}, {174, 128, 227, 2}, {174, 128, 227, 226}, {174, 128, 236, 98}, {174, 128, 242, 234}, {174, 128, 242, 250}, {174, 128, 243, 98}, {174, 128, 244, 74}, {174, 128, 245, 122}, {174, 128, 246, 10}, {199, 115, 98, 146}, {199, 115, 98, 234}, {199, 115, 101, 178}, {199, 115, 101, 186}, {199, 115, 102, 146}}},
|
||||||
{Region: "US East", IPs: []net.IP{{156, 146, 58, 202}, {156, 146, 58, 203}, {156, 146, 58, 204}, {156, 146, 58, 205}, {156, 146, 58, 206}, {156, 146, 58, 207}, {156, 146, 58, 208}, {156, 146, 58, 209}, {194, 59, 251, 5}, {194, 59, 251, 8}, {194, 59, 251, 25}, {194, 59, 251, 30}, {194, 59, 251, 38}, {194, 59, 251, 48}, {194, 59, 251, 49}, {194, 59, 251, 53}, {194, 59, 251, 66}, {194, 59, 251, 78}, {194, 59, 251, 79}, {194, 59, 251, 84}}},
|
{Region: "US East", IPs: []net.IP{{156, 146, 58, 202}, {156, 146, 58, 203}, {156, 146, 58, 204}, {156, 146, 58, 205}, {156, 146, 58, 207}, {156, 146, 58, 208}, {156, 146, 58, 209}, {193, 37, 253, 115}, {193, 37, 253, 134}, {194, 59, 251, 8}, {194, 59, 251, 11}, {194, 59, 251, 22}, {194, 59, 251, 28}, {194, 59, 251, 56}, {194, 59, 251, 62}, {194, 59, 251, 69}, {194, 59, 251, 82}, {194, 59, 251, 84}, {194, 59, 251, 91}, {194, 59, 251, 112}}},
|
||||||
{Region: "US Florida", IPs: []net.IP{{156, 146, 42, 65}, {156, 146, 42, 134}, {156, 146, 42, 198}, {156, 146, 43, 11}, {156, 146, 43, 75}, {193, 37, 252, 14}, {193, 37, 252, 15}, {193, 37, 252, 16}, {193, 37, 252, 17}, {193, 37, 252, 18}, {193, 37, 252, 19}, {193, 37, 252, 20}, {193, 37, 252, 21}, {193, 37, 252, 22}, {193, 37, 252, 23}, {193, 37, 252, 25}, {193, 37, 252, 26}, {193, 37, 252, 27}, {212, 102, 61, 19}, {212, 102, 61, 83}}},
|
{Region: "US Florida", IPs: []net.IP{{193, 37, 252, 6}, {193, 37, 252, 7}, {193, 37, 252, 8}, {193, 37, 252, 9}, {193, 37, 252, 10}, {193, 37, 252, 11}, {193, 37, 252, 12}, {193, 37, 252, 14}, {193, 37, 252, 15}, {193, 37, 252, 16}, {193, 37, 252, 17}, {193, 37, 252, 18}, {193, 37, 252, 19}, {193, 37, 252, 20}, {193, 37, 252, 21}, {193, 37, 252, 23}, {193, 37, 252, 24}, {193, 37, 252, 25}, {193, 37, 252, 26}, {193, 37, 252, 27}}},
|
||||||
{Region: "US Houston", IPs: []net.IP{{74, 81, 88, 26}, {74, 81, 88, 42}, {74, 81, 88, 66}, {74, 81, 88, 74}, {205, 251, 148, 90}, {205, 251, 148, 138}, {205, 251, 148, 154}, {205, 251, 148, 178}, {205, 251, 150, 146}, {205, 251, 150, 170}}},
|
{Region: "US Houston", IPs: []net.IP{{74, 81, 88, 26}, {74, 81, 88, 42}, {74, 81, 88, 66}, {74, 81, 88, 74}, {205, 251, 148, 66}, {205, 251, 148, 90}, {205, 251, 148, 98}, {205, 251, 148, 122}, {205, 251, 148, 130}, {205, 251, 148, 138}, {205, 251, 148, 186}, {205, 251, 150, 146}, {205, 251, 150, 170}}},
|
||||||
{Region: "US Las Vegas", IPs: []net.IP{{79, 110, 53, 34}, {79, 110, 53, 50}, {79, 110, 53, 66}, {79, 110, 53, 82}, {79, 110, 53, 98}, {79, 110, 53, 114}, {79, 110, 53, 130}, {79, 110, 53, 146}, {79, 110, 53, 162}, {79, 110, 53, 178}, {79, 110, 53, 194}, {79, 110, 53, 210}}},
|
{Region: "US Las Vegas", IPs: []net.IP{{79, 110, 53, 50}, {79, 110, 53, 66}, {79, 110, 53, 98}, {79, 110, 53, 114}, {79, 110, 53, 130}, {79, 110, 53, 146}, {79, 110, 53, 162}, {79, 110, 53, 178}, {79, 110, 53, 194}, {79, 110, 53, 210}, {162, 251, 236, 7}, {199, 127, 56, 83}, {199, 127, 56, 84}, {199, 127, 56, 87}, {199, 127, 56, 89}, {199, 127, 56, 90}}},
|
||||||
{Region: "US New York City", IPs: []net.IP{{156, 146, 36, 225}, {156, 146, 55, 198}}},
|
{Region: "US New York City", IPs: []net.IP{{156, 146, 36, 225}, {156, 146, 37, 129}, {156, 146, 58, 1}, {156, 146, 58, 134}}},
|
||||||
{Region: "US Seattle", IPs: []net.IP{{84, 17, 41, 96}, {156, 146, 48, 65}, {156, 146, 48, 135}, {156, 146, 48, 200}, {156, 146, 49, 13}, {212, 102, 46, 129}, {212, 102, 46, 193}, {212, 102, 47, 134}}},
|
{Region: "US Seattle", IPs: []net.IP{{156, 146, 48, 65}, {156, 146, 48, 135}, {156, 146, 48, 200}, {156, 146, 49, 13}, {212, 102, 46, 129}, {212, 102, 46, 193}, {212, 102, 47, 134}}},
|
||||||
{Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 133}, {199, 116, 118, 148}, {199, 116, 118, 167}, {199, 116, 118, 172}, {199, 116, 118, 173}, {199, 116, 118, 174}, {199, 116, 118, 185}, {199, 116, 118, 198}, {199, 116, 118, 202}, {199, 116, 118, 210}, {199, 116, 118, 212}, {199, 116, 118, 215}, {199, 116, 118, 217}, {199, 116, 118, 219}, {199, 116, 118, 220}, {199, 116, 118, 223}, {199, 116, 118, 237}, {199, 116, 118, 239}, {199, 116, 118, 240}, {199, 116, 118, 249}}},
|
{Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 130}, {199, 116, 118, 132}, {199, 116, 118, 134}, {199, 116, 118, 136}, {199, 116, 118, 145}, {199, 116, 118, 148}, {199, 116, 118, 149}, {199, 116, 118, 157}, {199, 116, 118, 166}, {199, 116, 118, 169}, {199, 116, 118, 172}}},
|
||||||
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 52}, {70, 32, 0, 53}, {70, 32, 0, 59}, {70, 32, 0, 60}, {70, 32, 0, 61}, {70, 32, 0, 64}, {70, 32, 0, 67}, {70, 32, 0, 68}, {70, 32, 0, 69}, {70, 32, 0, 70}, {70, 32, 0, 103}, {70, 32, 0, 106}, {70, 32, 0, 107}, {70, 32, 0, 114}, {70, 32, 0, 116}, {70, 32, 0, 120}, {70, 32, 0, 122}, {70, 32, 0, 168}, {70, 32, 0, 172}, {70, 32, 0, 173}}},
|
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 46}, {70, 32, 0, 51}, {70, 32, 0, 53}, {70, 32, 0, 62}, {70, 32, 0, 64}, {70, 32, 0, 68}, {70, 32, 0, 69}, {70, 32, 0, 72}, {70, 32, 0, 76}, {70, 32, 0, 77}, {70, 32, 0, 106}, {70, 32, 0, 107}, {70, 32, 0, 114}, {70, 32, 0, 116}, {70, 32, 0, 120}, {70, 32, 0, 167}, {70, 32, 0, 168}, {70, 32, 0, 170}, {70, 32, 0, 172}, {70, 32, 0, 173}}},
|
||||||
{Region: "US West", IPs: []net.IP{{104, 200, 151, 7}, {104, 200, 151, 8}, {104, 200, 151, 9}, {104, 200, 151, 11}, {104, 200, 151, 13}, {104, 200, 151, 16}, {104, 200, 151, 17}, {104, 200, 151, 20}, {104, 200, 151, 21}, {104, 200, 151, 46}, {104, 200, 151, 47}, {104, 200, 151, 50}, {104, 200, 151, 53}, {104, 200, 151, 56}, {104, 200, 151, 59}, {104, 200, 151, 61}, {104, 200, 151, 72}, {104, 200, 151, 74}, {104, 200, 151, 78}, {104, 200, 151, 81}}},
|
{Region: "US West", IPs: []net.IP{{184, 170, 241, 130}, {184, 170, 241, 194}, {184, 170, 242, 135}, {184, 170, 242, 199}}},
|
||||||
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 10}, {62, 149, 20, 40}}},
|
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 10}, {62, 149, 20, 40}}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ func GetAllServers() (allServers models.AllServers) {
|
|||||||
Servers: NordvpnServers(),
|
Servers: NordvpnServers(),
|
||||||
},
|
},
|
||||||
Pia: models.PiaServers{
|
Pia: models.PiaServers{
|
||||||
Version: 1,
|
Version: 2,
|
||||||
Timestamp: 1599323261,
|
Timestamp: 1602531173,
|
||||||
Servers: PIAServers(),
|
Servers: PIAServers(),
|
||||||
},
|
},
|
||||||
PiaOld: models.PiaServers{
|
PiaOld: models.PiaOldServers{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Timestamp: 1600458645,
|
Timestamp: 1602523433,
|
||||||
Servers: PIAOldServers(),
|
Servers: PIAOldServers(),
|
||||||
},
|
},
|
||||||
Purevpn: models.PurevpnServers{
|
Purevpn: models.PurevpnServers{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func Test_versions(t *testing.T) {
|
|||||||
assert.Equal(t, "e8eLGRpb1sNX8mDNPOjA6g", digestServerModelVersion(t, models.CyberghostServer{}, allServers.Cyberghost.Version))
|
assert.Equal(t, "e8eLGRpb1sNX8mDNPOjA6g", digestServerModelVersion(t, models.CyberghostServer{}, allServers.Cyberghost.Version))
|
||||||
assert.Equal(t, "4yL2lFcxXd/l1ByxBQ7d3g", digestServerModelVersion(t, models.MullvadServer{}, allServers.Mullvad.Version))
|
assert.Equal(t, "4yL2lFcxXd/l1ByxBQ7d3g", digestServerModelVersion(t, models.MullvadServer{}, allServers.Mullvad.Version))
|
||||||
assert.Equal(t, "fjzfUqJH0KvetGRdZYEtOg", digestServerModelVersion(t, models.NordvpnServer{}, allServers.Nordvpn.Version))
|
assert.Equal(t, "fjzfUqJH0KvetGRdZYEtOg", digestServerModelVersion(t, models.NordvpnServer{}, allServers.Nordvpn.Version))
|
||||||
assert.Equal(t, "gYO+bJZCtQvxVk2dTi5d5Q", digestServerModelVersion(t, models.PIAServer{}, allServers.Pia.Version))
|
assert.Equal(t, "1Ux7clCAJI6fwj0O61Dtpg", digestServerModelVersion(t, models.PIAServer{}, allServers.Pia.Version))
|
||||||
assert.Equal(t, "EZ/SBXQOCS/iJU7A9yc7vg", digestServerModelVersion(t, models.PurevpnServer{}, allServers.Purevpn.Version))
|
assert.Equal(t, "EZ/SBXQOCS/iJU7A9yc7vg", digestServerModelVersion(t, models.PurevpnServer{}, allServers.Purevpn.Version))
|
||||||
assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.SurfsharkServer{}, allServers.Surfshark.Version))
|
assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.SurfsharkServer{}, allServers.Surfshark.Version))
|
||||||
assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.VyprvpnServer{}, allServers.Vyprvpn.Version))
|
assert.Equal(t, "7yfMpHwzRpEngA/6nYsNag", digestServerModelVersion(t, models.VyprvpnServer{}, allServers.Vyprvpn.Version))
|
||||||
@@ -50,8 +50,8 @@ func Test_timestamps(t *testing.T) {
|
|||||||
assert.Equal(t, "EFMpdq2b9COLevjXmje5zg", digestServersTimestamp(t, allServers.Cyberghost.Servers, allServers.Cyberghost.Timestamp))
|
assert.Equal(t, "EFMpdq2b9COLevjXmje5zg", digestServersTimestamp(t, allServers.Cyberghost.Servers, allServers.Cyberghost.Timestamp))
|
||||||
assert.Equal(t, "EU4fTzD7jWC9N5kmN5bOEg", digestServersTimestamp(t, allServers.Mullvad.Servers, allServers.Mullvad.Timestamp))
|
assert.Equal(t, "EU4fTzD7jWC9N5kmN5bOEg", digestServersTimestamp(t, allServers.Mullvad.Servers, allServers.Mullvad.Timestamp))
|
||||||
assert.Equal(t, "OLI62FoTf2wis25Nw4FLpg", digestServersTimestamp(t, allServers.Nordvpn.Servers, allServers.Nordvpn.Timestamp))
|
assert.Equal(t, "OLI62FoTf2wis25Nw4FLpg", digestServersTimestamp(t, allServers.Nordvpn.Servers, allServers.Nordvpn.Timestamp))
|
||||||
assert.Equal(t, "hAjEIo6FIrUsJuRmKOKPzA", digestServersTimestamp(t, allServers.Pia.Servers, allServers.Pia.Timestamp))
|
assert.Equal(t, "beZCOXNWxzrPsUWCyQM99A", digestServersTimestamp(t, allServers.Pia.Servers, allServers.Pia.Timestamp))
|
||||||
assert.Equal(t, "CKszzgA7YX5zqxQGiiOL9g", digestServersTimestamp(t, allServers.PiaOld.Servers, allServers.PiaOld.Timestamp))
|
assert.Equal(t, "e8mWsWynkSUGiJLvjALRvQ", digestServersTimestamp(t, allServers.PiaOld.Servers, allServers.PiaOld.Timestamp))
|
||||||
assert.Equal(t, "kwJdVWTiBOspfrRwZIA+Sg", digestServersTimestamp(t, allServers.Purevpn.Servers, allServers.Purevpn.Timestamp))
|
assert.Equal(t, "kwJdVWTiBOspfrRwZIA+Sg", digestServersTimestamp(t, allServers.Purevpn.Servers, allServers.Purevpn.Timestamp))
|
||||||
assert.Equal(t, "q28ju2KJqLhrggJTTjXSiw", digestServersTimestamp(t, allServers.Surfshark.Servers, allServers.Surfshark.Timestamp))
|
assert.Equal(t, "q28ju2KJqLhrggJTTjXSiw", digestServersTimestamp(t, allServers.Surfshark.Servers, allServers.Surfshark.Timestamp))
|
||||||
assert.Equal(t, "KdIQWi2tYUM4aMXvWfVBEg", digestServersTimestamp(t, allServers.Vyprvpn.Servers, allServers.Vyprvpn.Timestamp))
|
assert.Equal(t, "KdIQWi2tYUM4aMXvWfVBEg", digestServersTimestamp(t, allServers.Vyprvpn.Servers, allServers.Vyprvpn.Timestamp))
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package constants
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Announcement is a message announcement
|
// Announcement is a message announcement
|
||||||
Announcement = "Update servers information see https://github.com/qdm12/gluetun/wiki/Update-servers-information"
|
Announcement = "Port forwarding is working for PIA v4 servers"
|
||||||
// AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd
|
// AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd
|
||||||
AnnouncementExpiration = "2020-10-10"
|
AnnouncementExpiration = "2020-11-15"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ type looper struct {
|
|||||||
start chan struct{}
|
start chan struct{}
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
updateTicker chan struct{}
|
updateTicker chan struct{}
|
||||||
|
timeNow func() time.Time
|
||||||
|
timeSince func(time.Time) time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
||||||
@@ -49,6 +51,8 @@ func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
|||||||
start: make(chan struct{}),
|
start: make(chan struct{}),
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
updateTicker: make(chan struct{}),
|
updateTicker: make(chan struct{}),
|
||||||
|
timeNow: time.Now,
|
||||||
|
timeSince: time.Since,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,24 +289,45 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
|||||||
|
|
||||||
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(time.Hour)
|
// Timer that acts as a ticker
|
||||||
|
timer := time.NewTimer(time.Hour)
|
||||||
|
timer.Stop()
|
||||||
|
timerIsStopped := true
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
if settings.UpdatePeriod > 0 {
|
if settings.UpdatePeriod > 0 {
|
||||||
ticker = time.NewTicker(settings.UpdatePeriod)
|
timer.Reset(settings.UpdatePeriod)
|
||||||
} else {
|
timerIsStopped = false
|
||||||
ticker.Stop()
|
|
||||||
}
|
}
|
||||||
|
lastTick := time.Unix(0, 0)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
ticker.Stop()
|
if !timerIsStopped && !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-timer.C:
|
||||||
|
lastTick = l.timeNow()
|
||||||
l.restart <- struct{}{}
|
l.restart <- struct{}{}
|
||||||
|
settings := l.GetSettings()
|
||||||
|
timer.Reset(settings.UpdatePeriod)
|
||||||
case <-l.updateTicker:
|
case <-l.updateTicker:
|
||||||
ticker.Stop()
|
if !timer.Stop() {
|
||||||
period := l.GetSettings().UpdatePeriod
|
<-timer.C
|
||||||
ticker = time.NewTicker(period)
|
}
|
||||||
|
timerIsStopped = true
|
||||||
|
settings := l.GetSettings()
|
||||||
|
newUpdatePeriod := settings.UpdatePeriod
|
||||||
|
if newUpdatePeriod == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var waited time.Duration
|
||||||
|
if lastTick.UnixNano() != 0 {
|
||||||
|
waited = l.timeSince(lastTick)
|
||||||
|
}
|
||||||
|
leftToWait := newUpdatePeriod - waited
|
||||||
|
timer.Reset(leftToWait)
|
||||||
|
timerIsStopped = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ func (c *configurator) enable(ctx context.Context) (err error) { //nolint:gocogn
|
|||||||
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
|
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
|
||||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||||
}
|
}
|
||||||
for _, conn := range c.vpnConnections {
|
if c.vpnConnection.IP != nil {
|
||||||
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, conn, remove); err != nil {
|
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
|
||||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
type Configurator interface {
|
type Configurator interface {
|
||||||
Version(ctx context.Context) (string, error)
|
Version(ctx context.Context) (string, error)
|
||||||
SetEnabled(ctx context.Context, enabled bool) (err error)
|
SetEnabled(ctx context.Context, enabled bool) (err error)
|
||||||
SetVPNConnections(ctx context.Context, connections []models.OpenVPNConnection) (err error)
|
SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error)
|
||||||
SetAllowedSubnets(ctx context.Context, subnets []net.IPNet) (err error)
|
SetAllowedSubnets(ctx context.Context, subnets []net.IPNet) (err error)
|
||||||
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
||||||
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
||||||
@@ -39,7 +39,7 @@ type configurator struct { //nolint:maligned
|
|||||||
|
|
||||||
// State
|
// State
|
||||||
enabled bool
|
enabled bool
|
||||||
vpnConnections []models.OpenVPNConnection
|
vpnConnection models.OpenVPNConnection
|
||||||
allowedSubnets []net.IPNet
|
allowedSubnets []net.IPNet
|
||||||
allowedInputPorts map[uint16]string // port to interface mapping
|
allowedInputPorts map[uint16]string // port to interface mapping
|
||||||
stateMutex sync.Mutex
|
stateMutex sync.Mutex
|
||||||
|
|||||||
@@ -7,95 +7,33 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *configurator) SetVPNConnections(ctx context.Context, connections []models.OpenVPNConnection) (err error) {
|
func (c *configurator) SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error) {
|
||||||
c.stateMutex.Lock()
|
c.stateMutex.Lock()
|
||||||
defer c.stateMutex.Unlock()
|
defer c.stateMutex.Unlock()
|
||||||
|
|
||||||
if !c.enabled {
|
if !c.enabled {
|
||||||
c.logger.Info("firewall disabled, only updating VPN connections internal list")
|
c.logger.Info("firewall disabled, only updating internal VPN connection")
|
||||||
c.vpnConnections = make([]models.OpenVPNConnection, len(connections))
|
c.vpnConnection = connection
|
||||||
copy(c.vpnConnections, connections)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Info("setting VPN connections through firewall...")
|
c.logger.Info("setting VPN connection through firewall...")
|
||||||
|
|
||||||
connectionsToAdd := findConnectionsToAdd(c.vpnConnections, connections)
|
if c.vpnConnection.Equal(connection) {
|
||||||
connectionsToRemove := findConnectionsToRemove(c.vpnConnections, connections)
|
|
||||||
if len(connectionsToAdd) == 0 && len(connectionsToRemove) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.removeConnections(ctx, connectionsToRemove, c.defaultInterface)
|
remove := true
|
||||||
if err := c.addConnections(ctx, connectionsToAdd, c.defaultInterface); err != nil {
|
if c.vpnConnection.IP != nil {
|
||||||
return fmt.Errorf("cannot set VPN connections through firewall: %w", err)
|
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeConnectionFromConnections(connections []models.OpenVPNConnection, connection models.OpenVPNConnection) []models.OpenVPNConnection {
|
|
||||||
L := len(connections)
|
|
||||||
for i := range connections {
|
|
||||||
if connection.Equal(connections[i]) {
|
|
||||||
connections[i] = connections[L-1]
|
|
||||||
connections = connections[:L-1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connections
|
|
||||||
}
|
|
||||||
|
|
||||||
func findConnectionsToAdd(oldConnections, newConnections []models.OpenVPNConnection) (connectionsToAdd []models.OpenVPNConnection) {
|
|
||||||
for _, newConnection := range newConnections {
|
|
||||||
found := false
|
|
||||||
for _, oldConnection := range oldConnections {
|
|
||||||
if oldConnection.Equal(newConnection) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
connectionsToAdd = append(connectionsToAdd, newConnection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connectionsToAdd
|
|
||||||
}
|
|
||||||
|
|
||||||
func findConnectionsToRemove(oldConnections, newConnections []models.OpenVPNConnection) (connectionsToRemove []models.OpenVPNConnection) {
|
|
||||||
for _, oldConnection := range oldConnections {
|
|
||||||
found := false
|
|
||||||
for _, newConnection := range newConnections {
|
|
||||||
if oldConnection.Equal(newConnection) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
connectionsToRemove = append(connectionsToRemove, oldConnection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connectionsToRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *configurator) removeConnections(ctx context.Context, connections []models.OpenVPNConnection, defaultInterface string) {
|
|
||||||
for _, conn := range connections {
|
|
||||||
const remove = true
|
|
||||||
if err := c.acceptOutputTrafficToVPN(ctx, defaultInterface, conn, remove); err != nil {
|
|
||||||
c.logger.Error("cannot remove outdated VPN connection through firewall: %s", err)
|
c.logger.Error("cannot remove outdated VPN connection through firewall: %s", err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
c.vpnConnections = removeConnectionFromConnections(c.vpnConnections, conn)
|
|
||||||
}
|
}
|
||||||
}
|
c.vpnConnection = models.OpenVPNConnection{}
|
||||||
|
remove = false
|
||||||
func (c *configurator) addConnections(ctx context.Context, connections []models.OpenVPNConnection, defaultInterface string) error {
|
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
|
||||||
const remove = false
|
return fmt.Errorf("cannot set VPN connection through firewall: %w", err)
|
||||||
for _, conn := range connections {
|
|
||||||
if err := c.acceptOutputTrafficToVPN(ctx, defaultInterface, conn, remove); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.vpnConnections = append(c.vpnConnections, conn)
|
|
||||||
}
|
}
|
||||||
|
c.vpnConnection = connection
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
29
internal/logging/duration.go
Normal file
29
internal/logging/duration.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package version
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_formatDuration(t *testing.T) {
|
func Test_FormatDuration(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
@@ -57,7 +57,7 @@ func Test_formatDuration(t *testing.T) {
|
|||||||
testCase := testCase
|
testCase := testCase
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := formatDuration(testCase.duration)
|
s := FormatDuration(testCase.duration)
|
||||||
assert.Equal(t, testCase.s, s)
|
assert.Equal(t, testCase.s, s)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,7 @@ func (p *ProviderSettings) String() string {
|
|||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
"Region: "+p.ServerSelection.Region,
|
"Region: "+p.ServerSelection.Region,
|
||||||
"Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset,
|
"Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset,
|
||||||
|
"Port forwarding: "+p.PortForwarding.String(),
|
||||||
)
|
)
|
||||||
case "mullvad":
|
case "mullvad":
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
|
|||||||
@@ -8,11 +8,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PIAServer struct {
|
type PIAServer struct {
|
||||||
|
Region string `json:"region"`
|
||||||
|
PortForward bool `json:"port_forward"`
|
||||||
|
OpenvpnUDP PIAServerOpenvpn `json:"openvpn_udp"`
|
||||||
|
OpenvpnTCP PIAServerOpenvpn `json:"openvpn_tcp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PIAServerOpenvpn struct {
|
||||||
|
IPs []net.IP `json:"ips"`
|
||||||
|
CN string `json:"cn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PIAServerOpenvpn) String() string {
|
||||||
|
return fmt.Sprintf("models.PIAServerOpenvpn{CN: %q, IPs: %s}", p.CN, goStringifyIPs(p.IPs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PIAServer) String() string {
|
||||||
|
return fmt.Sprintf("{Region: %q, PortForward: %t, OpenvpnUDP: %s, OpenvpnTCP: %s}",
|
||||||
|
p.Region, p.PortForward, p.OpenvpnUDP.String(), p.OpenvpnTCP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
type PIAOldServer struct {
|
||||||
IPs []net.IP `json:"ips"`
|
IPs []net.IP `json:"ips"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PIAServer) String() string {
|
func (p *PIAOldServer) String() string {
|
||||||
return fmt.Sprintf("{Region: %q, IPs: %s}", p.Region, goStringifyIPs(p.IPs))
|
return fmt.Sprintf("{Region: %q, IPs: %s}", p.Region, goStringifyIPs(p.IPs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_PIAServer_String(t *testing.T) {
|
func Test_PIAOldServer_String(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
server PIAServer
|
server PIAOldServer
|
||||||
s string
|
s string
|
||||||
}{
|
}{
|
||||||
"no ips": {
|
"no ips": {
|
||||||
server: PIAServer{Region: "a b"},
|
server: PIAOldServer{Region: "a b"},
|
||||||
s: `{Region: "a b", IPs: []net.IP{}}`,
|
s: `{Region: "a b", IPs: []net.IP{}}`,
|
||||||
},
|
},
|
||||||
"with ips": {
|
"with ips": {
|
||||||
server: PIAServer{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}},
|
server: PIAOldServer{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}},
|
||||||
s: `{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}`,
|
s: `{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type AllServers struct {
|
|||||||
Cyberghost CyberghostServers `json:"cyberghost"`
|
Cyberghost CyberghostServers `json:"cyberghost"`
|
||||||
Mullvad MullvadServers `json:"mullvad"`
|
Mullvad MullvadServers `json:"mullvad"`
|
||||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||||
PiaOld PiaServers `json:"piaOld"`
|
PiaOld PiaOldServers `json:"piaOld"`
|
||||||
Pia PiaServers `json:"pia"`
|
Pia PiaServers `json:"pia"`
|
||||||
Purevpn PurevpnServers `json:"purevpn"`
|
Purevpn PurevpnServers `json:"purevpn"`
|
||||||
Surfshark SurfsharkServers `json:"surfshark"`
|
Surfshark SurfsharkServers `json:"surfshark"`
|
||||||
@@ -28,6 +28,11 @@ type NordvpnServers struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Servers []NordvpnServer `json:"servers"`
|
Servers []NordvpnServer `json:"servers"`
|
||||||
}
|
}
|
||||||
|
type PiaOldServers struct {
|
||||||
|
Version uint16 `json:"version"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Servers []PIAOldServer `json:"servers"`
|
||||||
|
}
|
||||||
type PiaServers struct {
|
type PiaServers struct {
|
||||||
Version uint16 `json:"version"`
|
Version uint16 `json:"version"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package openvpn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"net"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,17 +11,17 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/files"
|
"github.com/qdm12/golibs/files"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/golibs/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Looper interface {
|
type Looper interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
Restart()
|
||||||
PortForward()
|
PortForward(vpnGatewayIP net.IP)
|
||||||
GetSettings() (settings settings.OpenVPN)
|
GetSettings() (settings settings.OpenVPN)
|
||||||
SetSettings(settings settings.OpenVPN)
|
SetSettings(settings settings.OpenVPN)
|
||||||
GetPortForwarded() (portForwarded uint16)
|
GetPortForwarded() (portForwarded uint16)
|
||||||
@@ -42,21 +43,22 @@ type looper struct {
|
|||||||
// Configurators
|
// Configurators
|
||||||
conf Configurator
|
conf Configurator
|
||||||
fw firewall.Configurator
|
fw firewall.Configurator
|
||||||
|
routing routing.Routing
|
||||||
// Other objects
|
// Other objects
|
||||||
logger logging.Logger
|
logger, pfLogger logging.Logger
|
||||||
client network.Client
|
client *http.Client
|
||||||
fileManager files.FileManager
|
fileManager files.FileManager
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// Internal channels
|
// Internal channels
|
||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
portForwardSignals chan struct{}
|
portForwardSignals chan net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
||||||
uid, gid int, allServers models.AllServers,
|
uid, gid int, allServers models.AllServers,
|
||||||
conf Configurator, fw firewall.Configurator,
|
conf Configurator, fw firewall.Configurator, routing routing.Routing,
|
||||||
logger logging.Logger, client network.Client, fileManager files.FileManager,
|
logger logging.Logger, client *http.Client, fileManager files.FileManager,
|
||||||
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
|
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
|
||||||
return &looper{
|
return &looper{
|
||||||
provider: provider,
|
provider: provider,
|
||||||
@@ -66,18 +68,20 @@ func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
|||||||
allServers: allServers,
|
allServers: allServers,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
fw: fw,
|
fw: fw,
|
||||||
|
routing: routing,
|
||||||
logger: logger.WithPrefix("openvpn: "),
|
logger: logger.WithPrefix("openvpn: "),
|
||||||
|
pfLogger: logger.WithPrefix("port forwarding: "),
|
||||||
client: client,
|
client: client,
|
||||||
fileManager: fileManager,
|
fileManager: fileManager,
|
||||||
streamMerger: streamMerger,
|
streamMerger: streamMerger,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
restart: make(chan struct{}),
|
restart: make(chan struct{}),
|
||||||
portForwardSignals: make(chan struct{}),
|
portForwardSignals: make(chan net.IP),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||||
func (l *looper) PortForward() { l.portForwardSignals <- struct{}{} }
|
func (l *looper) PortForward(vpnGateway net.IP) { l.portForwardSignals <- vpnGateway }
|
||||||
|
|
||||||
func (l *looper) GetSettings() (settings settings.OpenVPN) {
|
func (l *looper) GetSettings() (settings settings.OpenVPN) {
|
||||||
l.settingsMutex.RLock()
|
l.settingsMutex.RLock()
|
||||||
@@ -109,16 +113,16 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
l.allServersMutex.RLock()
|
l.allServersMutex.RLock()
|
||||||
providerConf := provider.New(l.provider, l.allServers)
|
providerConf := provider.New(l.provider, l.allServers, time.Now)
|
||||||
l.allServersMutex.RUnlock()
|
l.allServersMutex.RUnlock()
|
||||||
connections, err := providerConf.GetOpenVPNConnections(settings.Provider.ServerSelection)
|
connection, err := providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
l.cancel()
|
l.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lines := providerConf.BuildConf(
|
lines := providerConf.BuildConf(
|
||||||
connections,
|
connection,
|
||||||
settings.Verbosity,
|
settings.Verbosity,
|
||||||
l.uid,
|
l.uid,
|
||||||
l.gid,
|
l.gid,
|
||||||
@@ -139,7 +143,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.fw.SetVPNConnections(ctx, connections); err != nil {
|
if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
l.cancel()
|
l.cancel()
|
||||||
return
|
return
|
||||||
@@ -158,10 +162,12 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
// TODO have a way to disable pf with a context
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-l.portForwardSignals:
|
case gateway := <-l.portForwardSignals:
|
||||||
l.portForward(ctx, providerConf, l.client)
|
wg.Add(1)
|
||||||
|
go l.portForward(ctx, wg, providerConf, l.client, gateway)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(openvpnCtx)
|
}(openvpnCtx)
|
||||||
@@ -200,43 +206,25 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
|||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) portForward(ctx context.Context, providerConf provider.Provider, client network.Client) {
|
// portForward is a blocking operation which may or may not be infinite.
|
||||||
|
// You should therefore always call it in a goroutine
|
||||||
|
func (l *looper) portForward(ctx context.Context, wg *sync.WaitGroup,
|
||||||
|
providerConf provider.Provider, client *http.Client, gateway net.IP) {
|
||||||
|
defer wg.Done()
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
if !settings.Provider.PortForwarding.Enabled {
|
if !settings.Provider.PortForwarding.Enabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var port uint16
|
syncState := func(port uint16) (pfFilepath models.Filepath) {
|
||||||
err := fmt.Errorf("")
|
|
||||||
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)
|
|
||||||
l.portForwardedMutex.Lock()
|
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.portForwarded = port
|
||||||
l.portForwardedMutex.Unlock()
|
l.portForwardedMutex.Unlock()
|
||||||
|
settings := l.GetSettings()
|
||||||
filepath := settings.Provider.PortForwarding.Filepath
|
return settings.Provider.PortForwarding.Filepath
|
||||||
l.logger.Info("writing forwarded port to %s", filepath)
|
|
||||||
err = l.fileManager.WriteLinesToFile(
|
|
||||||
string(filepath), []string{fmt.Sprintf("%d", port)},
|
|
||||||
files.Ownership(l.uid, l.gid), files.Permissions(0400),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
l.logger.Error(err)
|
|
||||||
}
|
}
|
||||||
|
providerConf.PortForward(ctx,
|
||||||
|
client, l.fileManager, l.pfLogger,
|
||||||
|
gateway, l.fw, syncState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) GetPortForwarded() (portForwarded uint16) {
|
func (l *looper) GetPortForwarded() (portForwarded uint16) {
|
||||||
|
|||||||
@@ -53,9 +53,8 @@ func (r *reader) GetOpenVPNRoot() (root bool, err error) {
|
|||||||
return r.envParams.GetYesNo("OPENVPN_ROOT", libparams.Default("no"))
|
return r.envParams.GetYesNo("OPENVPN_ROOT", libparams.Default("no"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTargetIP obtains the IP address to choose from the list of IP addresses
|
// GetTargetIP obtains the IP address to override over the list of IP addresses filtered
|
||||||
// available for a particular region, from the environment variable
|
// from the environment variable OPENVPN_TARGET_IP
|
||||||
// OPENVPN_TARGET_IP
|
|
||||||
func (r *reader) GetTargetIP() (ip net.IP, err error) {
|
func (r *reader) GetTargetIP() (ip net.IP, err error) {
|
||||||
s, err := r.envParams.GetEnv("OPENVPN_TARGET_IP")
|
s, err := r.envParams.GetEnv("OPENVPN_TARGET_IP")
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cyberghost struct {
|
type cyberghost struct {
|
||||||
servers []models.CyberghostServer
|
servers []models.CyberghostServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCyberghost(servers []models.CyberghostServer) *cyberghost {
|
func newCyberghost(servers []models.CyberghostServer, timeNow timeNowFunc) *cyberghost {
|
||||||
return &cyberghost{
|
return &cyberghost{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,36 +42,27 @@ func (c *cyberghost) filterServers(region, group string) (servers []models.Cyber
|
|||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cyberghost) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
|
func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
|
||||||
servers := c.filterServers(selection.Region, selection.Group)
|
if selection.TargetIP != nil {
|
||||||
if len(servers) == 0 {
|
return models.OpenVPNConnection{IP: selection.TargetIP, Port: 443, Protocol: selection.Protocol}, nil
|
||||||
return nil, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
servers := c.filterServers(selection.Region, selection.Group)
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return connection, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
for _, IP := range server.IPs {
|
for _, IP := range server.IPs {
|
||||||
if selection.TargetIP != nil {
|
|
||||||
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})
|
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: 443, Protocol: selection.Protocol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
return pickRandomConnection(connections, c.randSource), nil
|
||||||
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connections) > 64 {
|
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -97,7 +96,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", connections[0].Protocol),
|
fmt.Sprintf("proto %s", connection.Protocol),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
@@ -107,9 +107,6 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
|
|||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -135,6 +132,8 @@ func (c *cyberghost) BuildConf(connections []models.OpenVPNConnection, verbosity
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cyberghost) GetPortForward(client network.Client) (port uint16, err error) {
|
func (c *cyberghost) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for cyberghost")
|
panic("port forwarding is not supported for cyberghost")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mullvad struct {
|
type mullvad struct {
|
||||||
servers []models.MullvadServer
|
servers []models.MullvadServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMullvad(servers []models.MullvadServer) *mullvad {
|
func newMullvad(servers []models.MullvadServer, timeNow timeNowFunc) *mullvad {
|
||||||
return &mullvad{
|
return &mullvad{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,45 +47,36 @@ func (m *mullvad) filterServers(country, city, isp string) (servers []models.Mul
|
|||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mullvad) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
|
func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
|
||||||
servers := m.filterServers(selection.Country, selection.City, selection.ISP)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for country %q, city %q and ISP %q", selection.Country, selection.City, selection.ISP)
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultPort uint16 = 1194
|
var defaultPort uint16 = 1194
|
||||||
if selection.Protocol == constants.TCP {
|
if selection.Protocol == constants.TCP {
|
||||||
defaultPort = 443
|
defaultPort = 443
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, server := range servers {
|
|
||||||
port := defaultPort
|
port := defaultPort
|
||||||
if selection.CustomPort > 0 {
|
if selection.CustomPort > 0 {
|
||||||
port = selection.CustomPort
|
port = selection.CustomPort
|
||||||
}
|
}
|
||||||
for _, IP := range server.IPs {
|
|
||||||
if selection.TargetIP != nil {
|
if selection.TargetIP != nil {
|
||||||
if selection.TargetIP.Equal(IP) {
|
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||||
return []models.OpenVPNConnection{{IP: IP, 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})
|
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
return pickRandomConnection(connections, m.randSource), nil
|
||||||
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connections) > 64 {
|
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
func (m *mullvad) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -108,7 +107,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", connections[0].Protocol),
|
fmt.Sprintf("proto %s", connection.Protocol),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
}
|
}
|
||||||
if extras.OpenVPNIPv6 {
|
if extras.OpenVPNIPv6 {
|
||||||
@@ -120,9 +120,6 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -134,6 +131,8 @@ func (m *mullvad) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mullvad) GetPortForward(client network.Client) (port uint16, err error) {
|
func (m *mullvad) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for mullvad")
|
panic("port forwarding is not supported for mullvad")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nordvpn struct {
|
type nordvpn struct {
|
||||||
servers []models.NordvpnServer
|
servers []models.NordvpnServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNordvpn(servers []models.NordvpnServer) *nordvpn {
|
func newNordvpn(servers []models.NordvpnServer, timeNow timeNowFunc) *nordvpn {
|
||||||
return &nordvpn{
|
return &nordvpn{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,12 +48,7 @@ func (n *nordvpn) filterServers(region string, protocol models.NetworkProtocol,
|
|||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
|
func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
|
||||||
servers := n.filterServers(selection.Region, selection.Protocol, selection.Number)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port uint16
|
var port uint16
|
||||||
switch {
|
switch {
|
||||||
case selection.Protocol == constants.UDP:
|
case selection.Protocol == constants.UDP:
|
||||||
@@ -53,31 +56,27 @@ func (n *nordvpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
|
|||||||
case selection.Protocol == constants.TCP:
|
case selection.Protocol == constants.TCP:
|
||||||
port = 443
|
port = 443
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
||||||
}
|
|
||||||
|
|
||||||
for _, server := range servers {
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
if selection.TargetIP.Equal(server.IP) {
|
|
||||||
return []models.OpenVPNConnection{{IP: server.IP, Port: port, Protocol: selection.Protocol}}, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: server.IP, Port: port, Protocol: selection.Protocol})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
if selection.TargetIP != nil {
|
||||||
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(connections) > 64 {
|
servers := n.filterServers(selection.Region, selection.Protocol, selection.Number)
|
||||||
connections = connections[:64]
|
if len(servers) == 0 {
|
||||||
|
return connection, fmt.Errorf("no server found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
return connections, nil
|
connections := make([]models.OpenVPNConnection, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
connections = append(connections, models.OpenVPNConnection{IP: servers[i].IP, Port: port, Protocol: selection.Protocol})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickRandomConnection(connections, n.randSource), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -114,16 +113,14 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", string(connections[0].Protocol)),
|
fmt.Sprintf("proto %s", string(connection.Protocol)),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -142,6 +139,8 @@ func (n *nordvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nordvpn) GetPortForward(client network.Client) (port uint16, err error) {
|
func (n *nordvpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for nordvpn")
|
panic("port forwarding is not supported for nordvpn")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +1,14 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/crypto/random"
|
|
||||||
"github.com/qdm12/golibs/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type pia struct {
|
func buildPIAConf(connection models.OpenVPNConnection, verbosity int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
random random.Random
|
|
||||||
servers []models.PIAServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPrivateInternetAccess(servers []models.PIAServer) *pia {
|
|
||||||
return &pia{
|
|
||||||
random: random.NewRandom(),
|
|
||||||
servers: servers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) filterServers(region string) (servers []models.PIAServer) {
|
|
||||||
if len(region) == 0 {
|
|
||||||
return p.servers
|
|
||||||
}
|
|
||||||
for _, server := range p.servers {
|
|
||||||
if strings.EqualFold(server.Region, region) {
|
|
||||||
return []models.PIAServer{server}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
|
|
||||||
servers := p.filterServers(selection.Region)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for region %q", selection.Region)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port uint16
|
|
||||||
switch selection.Protocol {
|
|
||||||
case constants.TCP:
|
|
||||||
switch selection.EncryptionPreset {
|
|
||||||
case constants.PIAEncryptionPresetNormal:
|
|
||||||
port = 502
|
|
||||||
case constants.PIAEncryptionPresetStrong:
|
|
||||||
port = 501
|
|
||||||
}
|
|
||||||
case constants.UDP:
|
|
||||||
switch selection.EncryptionPreset {
|
|
||||||
case constants.PIAEncryptionPresetNormal:
|
|
||||||
port = 1198
|
|
||||||
case constants.PIAEncryptionPresetStrong:
|
|
||||||
port = 1197
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if port == 0 {
|
|
||||||
return nil, fmt.Errorf("combination of protocol %q and encryption %q does not yield any port number", selection.Protocol, selection.EncryptionPreset)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, IP := range server.IPs {
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
if selection.TargetIP.Equal(IP) {
|
|
||||||
return []models.OpenVPNConnection{{IP: IP, Port: port, Protocol: selection.Protocol}}, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connections) > 64 {
|
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
|
||||||
var X509CRL, certificate string
|
var X509CRL, certificate string
|
||||||
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
|
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
@@ -131,7 +52,8 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", connections[0].Protocol),
|
fmt.Sprintf("proto %s", connection.Protocol),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
@@ -141,9 +63,6 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
|
|||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<crl-verify>",
|
"<crl-verify>",
|
||||||
"-----BEGIN X509 CRL-----",
|
"-----BEGIN X509 CRL-----",
|
||||||
@@ -161,28 +80,3 @@ func (p *pia) BuildConf(connections []models.OpenVPNConnection, verbosity, uid,
|
|||||||
}...)
|
}...)
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pia) GetPortForward(client network.Client) (port uint16, err error) {
|
|
||||||
b, err := p.random.GenerateRandomBytes(32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
clientID := hex.EncodeToString(b)
|
|
||||||
url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID)
|
|
||||||
content, status, err := client.GetContent(url) // TODO add ctx
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return 0, err
|
|
||||||
case status != http.StatusOK:
|
|
||||||
return 0, fmt.Errorf("status is %d for %s; does your PIA server support port forwarding?", status, url)
|
|
||||||
case len(content) == 0:
|
|
||||||
return 0, fmt.Errorf("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding")
|
|
||||||
}
|
|
||||||
body := struct {
|
|
||||||
Port uint16 `json:"port"`
|
|
||||||
}{}
|
|
||||||
if err := json.Unmarshal(content, &body); err != nil {
|
|
||||||
return 0, fmt.Errorf("port forwarding response: %w", err)
|
|
||||||
}
|
|
||||||
return body.Port, nil
|
|
||||||
}
|
|
||||||
|
|||||||
148
internal/provider/piav3.go
Normal file
148
internal/provider/piav3.go
Normal 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
505
internal/provider/piav4.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,38 +1,46 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider contains methods to read and modify the openvpn configuration to connect as a client
|
// Provider contains methods to read and modify the openvpn configuration to connect as a client
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error)
|
GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error)
|
||||||
BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
|
BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
|
||||||
GetPortForward(client network.Client) (port uint16, err error)
|
PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath))
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(provider models.VPNProvider, allServers models.AllServers) Provider {
|
func New(provider models.VPNProvider, allServers models.AllServers, timeNow timeNowFunc) Provider {
|
||||||
switch provider {
|
switch provider {
|
||||||
case constants.PrivateInternetAccess:
|
case constants.PrivateInternetAccess:
|
||||||
return newPrivateInternetAccess(allServers.Pia.Servers)
|
return newPrivateInternetAccessV4(allServers.Pia.Servers, timeNow)
|
||||||
case constants.PrivateInternetAccessOld:
|
case constants.PrivateInternetAccessOld:
|
||||||
return newPrivateInternetAccess(allServers.PiaOld.Servers)
|
return newPrivateInternetAccessV3(allServers.PiaOld.Servers, timeNow)
|
||||||
case constants.Mullvad:
|
case constants.Mullvad:
|
||||||
return newMullvad(allServers.Mullvad.Servers)
|
return newMullvad(allServers.Mullvad.Servers, timeNow)
|
||||||
case constants.Windscribe:
|
case constants.Windscribe:
|
||||||
return newWindscribe(allServers.Windscribe.Servers)
|
return newWindscribe(allServers.Windscribe.Servers, timeNow)
|
||||||
case constants.Surfshark:
|
case constants.Surfshark:
|
||||||
return newSurfshark(allServers.Surfshark.Servers)
|
return newSurfshark(allServers.Surfshark.Servers, timeNow)
|
||||||
case constants.Cyberghost:
|
case constants.Cyberghost:
|
||||||
return newCyberghost(allServers.Cyberghost.Servers)
|
return newCyberghost(allServers.Cyberghost.Servers, timeNow)
|
||||||
case constants.Vyprvpn:
|
case constants.Vyprvpn:
|
||||||
return newVyprvpn(allServers.Vyprvpn.Servers)
|
return newVyprvpn(allServers.Vyprvpn.Servers, timeNow)
|
||||||
case constants.Nordvpn:
|
case constants.Nordvpn:
|
||||||
return newNordvpn(allServers.Nordvpn.Servers)
|
return newNordvpn(allServers.Nordvpn.Servers, timeNow)
|
||||||
case constants.Purevpn:
|
case constants.Purevpn:
|
||||||
return newPurevpn(allServers.Purevpn.Servers)
|
return newPurevpn(allServers.Purevpn.Servers, timeNow)
|
||||||
default:
|
default:
|
||||||
return nil // should never occur
|
return nil // should never occur
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type purevpn struct {
|
type purevpn struct {
|
||||||
servers []models.PurevpnServer
|
servers []models.PurevpnServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPurevpn(servers []models.PurevpnServer) *purevpn {
|
func newPurevpn(servers []models.PurevpnServer, timeNow timeNowFunc) *purevpn {
|
||||||
return &purevpn{
|
return &purevpn{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,12 +47,7 @@ func (p *purevpn) filterServers(region, country, city string) (servers []models.
|
|||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
|
func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
|
||||||
servers := p.filterServers(selection.Region, selection.Country, selection.City)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for region %q, country %q and city %q", selection.Region, selection.Country, selection.City)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port uint16
|
var port uint16
|
||||||
switch {
|
switch {
|
||||||
case selection.Protocol == constants.UDP:
|
case selection.Protocol == constants.UDP:
|
||||||
@@ -52,33 +55,29 @@ func (p *purevpn) GetOpenVPNConnections(selection models.ServerSelection) (conne
|
|||||||
case selection.Protocol == constants.TCP:
|
case selection.Protocol == constants.TCP:
|
||||||
port = 80
|
port = 80
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _, server := range servers {
|
||||||
for _, IP := range server.IPs {
|
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})
|
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
return pickRandomConnection(connections, p.randSource), nil
|
||||||
return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connections) > 64 {
|
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -109,15 +108,13 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", string(connections[0].Protocol)),
|
fmt.Sprintf("proto %s", string(connection.Protocol)),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -151,12 +148,14 @@ func (p *purevpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
if len(auth) > 0 {
|
if len(auth) > 0 {
|
||||||
lines = append(lines, "auth "+auth)
|
lines = append(lines, "auth "+auth)
|
||||||
}
|
}
|
||||||
if connections[0].Protocol == constants.UDP {
|
if connection.Protocol == constants.UDP {
|
||||||
lines = append(lines, "explicit-exit-notify")
|
lines = append(lines, "explicit-exit-notify")
|
||||||
}
|
}
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *purevpn) GetPortForward(client network.Client) (port uint16, err error) {
|
func (p *purevpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for purevpn")
|
panic("port forwarding is not supported for purevpn")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type surfshark struct {
|
type surfshark struct {
|
||||||
servers []models.SurfsharkServer
|
servers []models.SurfsharkServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSurfshark(servers []models.SurfsharkServer) *surfshark {
|
func newSurfshark(servers []models.SurfsharkServer, timeNow timeNowFunc) *surfshark {
|
||||||
return &surfshark{
|
return &surfshark{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +39,7 @@ func (s *surfshark) filterServers(region string) (servers []models.SurfsharkServ
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { //nolint:dupl
|
func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl
|
||||||
servers := s.filterServers(selection.Region)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for region %q", selection.Region)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port uint16
|
var port uint16
|
||||||
switch {
|
switch {
|
||||||
case selection.Protocol == constants.TCP:
|
case selection.Protocol == constants.TCP:
|
||||||
@@ -44,33 +47,33 @@ func (s *surfshark) GetOpenVPNConnections(selection models.ServerSelection) (con
|
|||||||
case selection.Protocol == constants.UDP:
|
case selection.Protocol == constants.UDP:
|
||||||
port = 1194
|
port = 1194
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
servers := s.filterServers(selection.Region)
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return connection, fmt.Errorf("no server found for region %q", selection.Region)
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
for _, IP := range server.IPs {
|
for _, IP := range server.IPs {
|
||||||
if selection.TargetIP != nil {
|
|
||||||
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})
|
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
if selection.TargetIP != nil {
|
||||||
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
return connection, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(connections) > 64 {
|
return pickRandomConnection(connections, s.randSource), nil
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { //nolint:dupl
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -107,16 +110,14 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", connections[0].Protocol),
|
fmt.Sprintf("proto %s", connection.Protocol),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -135,6 +136,8 @@ func (s *surfshark) BuildConf(connections []models.OpenVPNConnection, verbosity,
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surfshark) GetPortForward(client network.Client) (port uint16, err error) {
|
func (s *surfshark) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for surfshark")
|
panic("port forwarding is not supported for surfshark")
|
||||||
}
|
}
|
||||||
|
|||||||
37
internal/provider/utils.go
Normal file
37
internal/provider/utils.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type vyprvpn struct {
|
type vyprvpn struct {
|
||||||
servers []models.VyprvpnServer
|
servers []models.VyprvpnServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVyprvpn(servers []models.VyprvpnServer) *vyprvpn {
|
func newVyprvpn(servers []models.VyprvpnServer, timeNow timeNowFunc) *vyprvpn {
|
||||||
return &vyprvpn{
|
return &vyprvpn{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,46 +39,37 @@ func (v *vyprvpn) filterServers(region string) (servers []models.VyprvpnServer)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
|
func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
|
||||||
servers := v.filterServers(selection.Region)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for region %q", selection.Region)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port uint16
|
var port uint16
|
||||||
switch {
|
switch {
|
||||||
case selection.Protocol == constants.TCP:
|
case selection.Protocol == constants.TCP:
|
||||||
return nil, fmt.Errorf("TCP protocol not supported by this VPN provider")
|
return connection, fmt.Errorf("TCP protocol not supported by this VPN provider")
|
||||||
case selection.Protocol == constants.UDP:
|
case selection.Protocol == constants.UDP:
|
||||||
port = 443
|
port = 443
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _, server := range servers {
|
||||||
for _, IP := range server.IPs {
|
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})
|
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
return pickRandomConnection(connections, v.randSource), nil
|
||||||
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connections) > 64 {
|
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -101,16 +100,14 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", connections[0].Protocol),
|
fmt.Sprintf("proto %s", connection.Protocol),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -121,6 +118,8 @@ func (v *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, u
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *vyprvpn) GetPortForward(client network.Client) (port uint16, err error) {
|
func (v *vyprvpn) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for vyprvpn")
|
panic("port forwarding is not supported for vyprvpn")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/network"
|
"github.com/qdm12/golibs/files"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type windscribe struct {
|
type windscribe struct {
|
||||||
servers []models.WindscribeServer
|
servers []models.WindscribeServer
|
||||||
|
randSource rand.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindscribe(servers []models.WindscribeServer) *windscribe {
|
func newWindscribe(servers []models.WindscribeServer, timeNow timeNowFunc) *windscribe {
|
||||||
return &windscribe{
|
return &windscribe{
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +39,7 @@ func (w *windscribe) filterServers(region string) (servers []models.WindscribeSe
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) {
|
func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) {
|
||||||
servers := w.filterServers(selection.Region)
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no server found for region %q", selection.Region)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port uint16
|
var port uint16
|
||||||
switch {
|
switch {
|
||||||
case selection.CustomPort > 0:
|
case selection.CustomPort > 0:
|
||||||
@@ -46,33 +49,29 @@ func (w *windscribe) GetOpenVPNConnections(selection models.ServerSelection) (co
|
|||||||
case selection.Protocol == constants.UDP:
|
case selection.Protocol == constants.UDP:
|
||||||
port = 443
|
port = 443
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
return connection, fmt.Errorf("protocol %q is unknown", selection.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _, server := range servers {
|
||||||
for _, IP := range server.IPs {
|
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})
|
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if selection.TargetIP != nil {
|
return pickRandomConnection(connections, w.randSource), nil
|
||||||
return nil, fmt.Errorf("target IP %s not found in IP addresses", selection.TargetIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connections) > 64 {
|
|
||||||
connections = connections[:64]
|
|
||||||
}
|
|
||||||
|
|
||||||
return connections, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
func (w *windscribe) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -102,7 +101,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||||
fmt.Sprintf("proto %s", connections[0].Protocol),
|
fmt.Sprintf("proto %s", connection.Protocol),
|
||||||
|
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
@@ -112,9 +112,6 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
|
|||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user nonrootuser")
|
||||||
}
|
}
|
||||||
for _, connection := range connections {
|
|
||||||
lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
|
||||||
}
|
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
"-----BEGIN CERTIFICATE-----",
|
"-----BEGIN CERTIFICATE-----",
|
||||||
@@ -133,6 +130,8 @@ func (w *windscribe) BuildConf(connections []models.OpenVPNConnection, verbosity
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *windscribe) GetPortForward(client network.Client) (port uint16, err error) {
|
func (w *windscribe) PortForward(ctx context.Context, client *http.Client,
|
||||||
|
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for windscribe")
|
panic("port forwarding is not supported for windscribe")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ type looper struct {
|
|||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
updateTicker chan struct{}
|
updateTicker chan struct{}
|
||||||
|
timeNow func() time.Time
|
||||||
|
timeSince func(time.Time) time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
|
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
|
||||||
@@ -47,6 +49,8 @@ func NewLooper(client network.Client, logger logging.Logger, fileManager files.F
|
|||||||
restart: make(chan struct{}),
|
restart: make(chan struct{}),
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
updateTicker: make(chan struct{}),
|
updateTicker: make(chan struct{}),
|
||||||
|
timeNow: time.Now,
|
||||||
|
timeSince: time.Since,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,23 +131,42 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
|
|
||||||
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(time.Hour)
|
timer := time.NewTimer(time.Hour)
|
||||||
|
timer.Stop() // 1 hour, cannot be a race condition
|
||||||
|
timerIsStopped := true
|
||||||
period := l.GetPeriod()
|
period := l.GetPeriod()
|
||||||
if period > 0 {
|
if period > 0 {
|
||||||
ticker = time.NewTicker(period)
|
timer.Reset(period)
|
||||||
} else {
|
timerIsStopped = false
|
||||||
ticker.Stop()
|
|
||||||
}
|
}
|
||||||
|
lastTick := time.Unix(0, 0)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
ticker.Stop()
|
if !timerIsStopped && !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-timer.C:
|
||||||
|
lastTick = l.timeNow()
|
||||||
l.restart <- struct{}{}
|
l.restart <- struct{}{}
|
||||||
|
timer.Reset(l.GetPeriod())
|
||||||
case <-l.updateTicker:
|
case <-l.updateTicker:
|
||||||
ticker.Stop()
|
if !timer.Stop() {
|
||||||
ticker = time.NewTicker(l.GetPeriod())
|
<-timer.C
|
||||||
|
}
|
||||||
|
timerIsStopped = true
|
||||||
|
period := l.GetPeriod()
|
||||||
|
if period == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var waited time.Duration
|
||||||
|
if lastTick.UnixNano() > 0 {
|
||||||
|
waited = l.timeSince(lastTick)
|
||||||
|
}
|
||||||
|
leftToWait := period - waited
|
||||||
|
timer.Reset(leftToWait)
|
||||||
|
timerIsStopped = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/golibs/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
|
func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
|
||||||
@@ -23,12 +24,16 @@ func parseRoutingTable(data []byte) (entries []routingEntry, err error) {
|
|||||||
return entries, nil
|
return entries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) {
|
func getRoutingEntries(fileManager files.FileManager) (entries []routingEntry, err error) {
|
||||||
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
|
data, err := fileManager.ReadFile(string(constants.NetRoute))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
entries, err := parseRoutingTable(data)
|
return parseRoutingTable(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) {
|
||||||
|
entries, err := getRoutingEntries(r.fileManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -52,11 +57,7 @@ func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
|
func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
|
||||||
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
|
entries, err := getRoutingEntries(r.fileManager)
|
||||||
if err != nil {
|
|
||||||
return defaultSubnet, err
|
|
||||||
}
|
|
||||||
entries, err := parseRoutingTable(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return defaultSubnet, err
|
return defaultSubnet, err
|
||||||
}
|
}
|
||||||
@@ -79,11 +80,7 @@ func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) {
|
func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) {
|
||||||
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
|
entries, err := getRoutingEntries(r.fileManager)
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("cannot check route existence: %w", err)
|
|
||||||
}
|
|
||||||
entries, err := parseRoutingTable(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("cannot check route existence: %w", err)
|
return false, fmt.Errorf("cannot check route existence: %w", err)
|
||||||
}
|
}
|
||||||
@@ -96,12 +93,8 @@ func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *routing) VPNGatewayIP(defaultInterface string) (ip net.IP, err error) {
|
func (r *routing) VPNDestinationIP(defaultInterface string) (ip net.IP, err error) {
|
||||||
data, err := r.fileManager.ReadFile(string(constants.NetRoute))
|
entries, err := getRoutingEntries(r.fileManager)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err)
|
|
||||||
}
|
|
||||||
entries, err := parseRoutingTable(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err)
|
return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err)
|
||||||
}
|
}
|
||||||
@@ -115,6 +108,20 @@ func (r *routing) VPNGatewayIP(defaultInterface string) (ip net.IP, err error) {
|
|||||||
return nil, fmt.Errorf("cannot find VPN gateway IP address from ip routes")
|
return nil, fmt.Errorf("cannot find VPN gateway IP address from ip routes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) {
|
||||||
|
entries, err := getRoutingEntries(r.fileManager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot find VPN local gateway IP address: %w", err)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.iface == string(constants.TUN) &&
|
||||||
|
entry.destination.Equal(net.IP{0, 0, 0, 0}) {
|
||||||
|
return entry.gateway, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes")
|
||||||
|
}
|
||||||
|
|
||||||
func ipIsPrivate(ip net.IP) bool {
|
func ipIsPrivate(ip net.IP) bool {
|
||||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ eth0 0002A8C0 0100000A 0003 0 0 0 00FFFFFF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_VPNGatewayIP(t *testing.T) {
|
func Test_VPNDestinationIP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
@@ -334,7 +334,7 @@ eth0 x
|
|||||||
filemanager.EXPECT().ReadFile(string(constants.NetRoute)).
|
filemanager.EXPECT().ReadFile(string(constants.NetRoute)).
|
||||||
Return(tc.data, tc.readErr).Times(1)
|
Return(tc.data, tc.readErr).Times(1)
|
||||||
r := &routing{fileManager: filemanager}
|
r := &routing{fileManager: filemanager}
|
||||||
ip, err := r.VPNGatewayIP(tc.defaultInterface)
|
ip, err := r.VPNDestinationIP(tc.defaultInterface)
|
||||||
if tc.err != nil {
|
if tc.err != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, tc.err.Error(), err.Error())
|
assert.Equal(t, tc.err.Error(), err.Error())
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ type Routing interface {
|
|||||||
DeleteRouteVia(ctx context.Context, subnet net.IPNet) (err error)
|
DeleteRouteVia(ctx context.Context, subnet net.IPNet) (err error)
|
||||||
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
|
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
|
||||||
LocalSubnet() (defaultSubnet net.IPNet, err error)
|
LocalSubnet() (defaultSubnet net.IPNet, err error)
|
||||||
VPNGatewayIP(defaultInterface string) (ip net.IP, err error)
|
VPNDestinationIP(defaultInterface string) (ip net.IP, err error)
|
||||||
|
VPNLocalGatewayIP() (ip net.IP, err error)
|
||||||
SetDebug()
|
SetDebug()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,16 @@ import (
|
|||||||
|
|
||||||
// GetPIASettings obtains PIA settings from environment variables using the params package.
|
// GetPIASettings obtains PIA settings from environment variables using the params package.
|
||||||
func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
|
func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
|
||||||
settings.Name = constants.PrivateInternetAccess
|
return getPIASettings(paramsReader, constants.PrivateInternetAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPIAOldSettings obtains PIA settings for the older PIA servers (pre summer 2020) from environment variables using the params package.
|
||||||
|
func GetPIAOldSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
|
||||||
|
return getPIASettings(paramsReader, constants.PrivateInternetAccessOld)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPIASettings(paramsReader params.Reader, name models.VPNProvider) (settings models.ProviderSettings, err error) {
|
||||||
|
settings.Name = name
|
||||||
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
|
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
@@ -29,30 +38,6 @@ func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSetting
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
return settings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPIAOldSettings obtains PIA settings for the older PIA servers (pre summer 2020) from environment variables using the params package.
|
|
||||||
func GetPIAOldSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) {
|
|
||||||
settings.Name = constants.PrivateInternetAccessOld
|
|
||||||
settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
encryptionPreset, err := paramsReader.GetPIAEncryptionPreset()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.ServerSelection.EncryptionPreset = encryptionPreset
|
|
||||||
settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset
|
|
||||||
settings.ServerSelection.Region, err = paramsReader.GetPIAOldRegion()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.PortForwarding.Enabled, err = paramsReader.GetPortForwarding()
|
settings.PortForwarding.Enabled, err = paramsReader.GetPortForwarding()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
|
|||||||
@@ -36,10 +36,17 @@ func (s *storage) mergeServers(hardcoded, persistent models.AllServers) (merged
|
|||||||
}
|
}
|
||||||
merged.Pia = hardcoded.Pia
|
merged.Pia = hardcoded.Pia
|
||||||
if persistent.Pia.Timestamp > hardcoded.Pia.Timestamp {
|
if persistent.Pia.Timestamp > hardcoded.Pia.Timestamp {
|
||||||
|
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)",
|
s.logger.Info("Using Private Internet Access servers from file (%s more recent)",
|
||||||
getUnixTimeDifference(persistent.Pia.Timestamp, hardcoded.Pia.Timestamp))
|
getUnixTimeDifference(persistent.Pia.Timestamp, hardcoded.Pia.Timestamp))
|
||||||
merged.Pia = persistent.Pia
|
merged.Pia = persistent.Pia
|
||||||
}
|
}
|
||||||
|
}
|
||||||
merged.PiaOld = hardcoded.PiaOld
|
merged.PiaOld = hardcoded.PiaOld
|
||||||
if persistent.PiaOld.Timestamp > hardcoded.PiaOld.Timestamp {
|
if persistent.PiaOld.Timestamp > hardcoded.PiaOld.Timestamp {
|
||||||
s.logger.Info("Using Private Internet Access older servers from file (%s more recent)",
|
s.logger.Info("Using Private Internet Access older servers from file (%s more recent)",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers
|
|||||||
results := make(chan models.CyberghostServer)
|
results := make(chan models.CyberghostServer)
|
||||||
const maxGoroutines = 10
|
const maxGoroutines = 10
|
||||||
guard := make(chan struct{}, maxGoroutines)
|
guard := make(chan struct{}, maxGoroutines)
|
||||||
|
defer close(guard)
|
||||||
for groupID, groupName := range groups {
|
for groupID, groupName := range groups {
|
||||||
for countryCode, region := range possibleCountryCodes {
|
for countryCode, region := range possibleCountryCodes {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
@@ -37,9 +38,7 @@ func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers
|
|||||||
}
|
}
|
||||||
const domain = "cg-dialup.net"
|
const domain = "cg-dialup.net"
|
||||||
host := fmt.Sprintf("%s-%s.%s", groupID, countryCode, domain)
|
host := fmt.Sprintf("%s-%s.%s", groupID, countryCode, domain)
|
||||||
guard <- struct{}{}
|
go tryCyberghostHostname(ctx, lookupIP, host, groupName, region, results, guard)
|
||||||
go tryCyberghostHostname(ctx, lookupIP, host, groupName, region, results)
|
|
||||||
<-guard
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < len(groups)*len(possibleCountryCodes); i++ {
|
for i := 0; i < len(groups)*len(possibleCountryCodes); i++ {
|
||||||
@@ -60,7 +59,11 @@ func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers
|
|||||||
|
|
||||||
func tryCyberghostHostname(ctx context.Context, lookupIP lookupIPFunc,
|
func tryCyberghostHostname(ctx context.Context, lookupIP lookupIPFunc,
|
||||||
host, groupName, region string,
|
host, groupName, region string,
|
||||||
results chan<- models.CyberghostServer) {
|
results chan<- models.CyberghostServer, guard chan struct{}) {
|
||||||
|
guard <- struct{}{}
|
||||||
|
defer func() {
|
||||||
|
<-guard
|
||||||
|
}()
|
||||||
IPs, err := resolveRepeat(ctx, lookupIP, host, 2)
|
IPs, err := resolveRepeat(ctx, lookupIP, host, 2)
|
||||||
if err != nil || len(IPs) == 0 {
|
if err != nil || len(IPs) == 0 {
|
||||||
results <- models.CyberghostServer{}
|
results <- models.CyberghostServer{}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ type looper struct {
|
|||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
updateTicker chan struct{}
|
updateTicker chan struct{}
|
||||||
|
timeNow func() time.Time
|
||||||
|
timeSince func(time.Time) time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(options Options, period time.Duration, currentServers models.AllServers,
|
func NewLooper(options Options, period time.Duration, currentServers models.AllServers,
|
||||||
@@ -45,6 +47,8 @@ func NewLooper(options Options, period time.Duration, currentServers models.AllS
|
|||||||
restart: make(chan struct{}),
|
restart: make(chan struct{}),
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
updateTicker: make(chan struct{}),
|
updateTicker: make(chan struct{}),
|
||||||
|
timeNow: time.Now,
|
||||||
|
timeSince: time.Since,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,23 +129,41 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
|
|
||||||
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(time.Hour)
|
timer := time.NewTimer(time.Hour)
|
||||||
period := l.GetPeriod()
|
timer.Stop()
|
||||||
if period > 0 {
|
timerIsStopped := true
|
||||||
ticker = time.NewTicker(period)
|
if period := l.GetPeriod(); period > 0 {
|
||||||
} else {
|
timerIsStopped = false
|
||||||
ticker.Stop()
|
timer.Reset(period)
|
||||||
}
|
}
|
||||||
|
lastTick := time.Unix(0, 0)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
ticker.Stop()
|
if !timerIsStopped && !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-timer.C:
|
||||||
|
lastTick = l.timeNow()
|
||||||
l.restart <- struct{}{}
|
l.restart <- struct{}{}
|
||||||
|
timer.Reset(l.GetPeriod())
|
||||||
case <-l.updateTicker:
|
case <-l.updateTicker:
|
||||||
ticker.Stop()
|
if !timerIsStopped && !timer.Stop() {
|
||||||
ticker = time.NewTicker(l.GetPeriod())
|
<-timer.C
|
||||||
|
}
|
||||||
|
timerIsStopped = true
|
||||||
|
period := l.GetPeriod()
|
||||||
|
if period == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var waited time.Duration
|
||||||
|
if lastTick.UnixNano() > 0 {
|
||||||
|
waited = l.timeSince(lastTick)
|
||||||
|
}
|
||||||
|
leftToWait := period - waited
|
||||||
|
timer.Reset(leftToWait)
|
||||||
|
timerIsStopped = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,18 +14,6 @@ func extractRemoteLinesFromOpenvpn(content []byte) (remoteLines []string) {
|
|||||||
return remoteLines
|
return remoteLines
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractIPsFromRemoteLines(remoteLines []string) (ips []net.IP) {
|
|
||||||
for _, remoteLine := range remoteLines {
|
|
||||||
fields := strings.Fields(remoteLine)
|
|
||||||
ip := net.ParseIP(fields[1])
|
|
||||||
if ip == nil { // not an IP address
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ips = append(ips, ip)
|
|
||||||
}
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractHostnamesFromRemoteLines(remoteLines []string) (hostnames []string) {
|
func extractHostnamesFromRemoteLines(remoteLines []string) (hostnames []string) {
|
||||||
for _, remoteLine := range remoteLines {
|
for _, remoteLine := range remoteLines {
|
||||||
fields := strings.Fields(remoteLine)
|
fields := strings.Fields(remoteLine)
|
||||||
|
|||||||
@@ -11,40 +11,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *updater) updatePIA() (err error) {
|
|
||||||
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn-ip-nextgen.zip"
|
|
||||||
contents, err := fetchAndExtractFiles(zipURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
servers := make([]models.PIAServer, 0, len(contents))
|
|
||||||
for fileName, content := range contents {
|
|
||||||
remoteLines := extractRemoteLinesFromOpenvpn(content)
|
|
||||||
if len(remoteLines) == 0 {
|
|
||||||
return fmt.Errorf("cannot find any remote lines in %s", fileName)
|
|
||||||
}
|
|
||||||
IPs := extractIPsFromRemoteLines(remoteLines)
|
|
||||||
if len(IPs) == 0 {
|
|
||||||
return fmt.Errorf("cannot find any IP addresses in %s", fileName)
|
|
||||||
}
|
|
||||||
region := strings.TrimSuffix(fileName, ".ovpn")
|
|
||||||
server := models.PIAServer{
|
|
||||||
Region: region,
|
|
||||||
IPs: uniqueSortedIPs(IPs),
|
|
||||||
}
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
sort.Slice(servers, func(i, j int) bool {
|
|
||||||
return servers[i].Region < servers[j].Region
|
|
||||||
})
|
|
||||||
if u.options.Stdout {
|
|
||||||
u.println(stringifyPIAServers(servers))
|
|
||||||
}
|
|
||||||
u.servers.Pia.Timestamp = u.timeNow().Unix()
|
|
||||||
u.servers.Pia.Servers = servers
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *updater) updatePIAOld(ctx context.Context) (err error) {
|
func (u *updater) updatePIAOld(ctx context.Context) (err error) {
|
||||||
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn.zip"
|
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn.zip"
|
||||||
contents, err := fetchAndExtractFiles(zipURL)
|
contents, err := fetchAndExtractFiles(zipURL)
|
||||||
@@ -54,8 +20,8 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
|
|||||||
const maxGoroutines = 10
|
const maxGoroutines = 10
|
||||||
guard := make(chan struct{}, maxGoroutines)
|
guard := make(chan struct{}, maxGoroutines)
|
||||||
errors := make(chan error)
|
errors := make(chan error)
|
||||||
serversCh := make(chan models.PIAServer)
|
serversCh := make(chan models.PIAOldServer)
|
||||||
servers := make([]models.PIAServer, 0, len(contents))
|
servers := make([]models.PIAOldServer, 0, len(contents))
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -75,10 +41,8 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
|
|||||||
return fmt.Errorf("cannot find any hosts in %s", fileName)
|
return fmt.Errorf("cannot find any hosts in %s", fileName)
|
||||||
}
|
}
|
||||||
region := strings.TrimSuffix(fileName, ".ovpn")
|
region := strings.TrimSuffix(fileName, ".ovpn")
|
||||||
guard <- struct{}{}
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go resolvePIAHostname(ctx, wg, region, hosts, u.lookupIP, errors, serversCh)
|
go resolvePIAv3Hostname(ctx, wg, region, hosts, u.lookupIP, errors, serversCh, guard)
|
||||||
<-guard
|
|
||||||
}
|
}
|
||||||
for range contents {
|
for range contents {
|
||||||
select {
|
select {
|
||||||
@@ -99,10 +63,14 @@ func (u *updater) updatePIAOld(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolvePIAHostname(ctx context.Context, wg *sync.WaitGroup,
|
func resolvePIAv3Hostname(ctx context.Context, wg *sync.WaitGroup,
|
||||||
region string, hosts []string, lookupIP lookupIPFunc,
|
region string, hosts []string, lookupIP lookupIPFunc,
|
||||||
errors chan<- error, serversCh chan<- models.PIAServer) {
|
errors chan<- error, serversCh chan<- models.PIAOldServer, guard chan struct{}) {
|
||||||
defer wg.Done()
|
guard <- struct{}{}
|
||||||
|
defer func() {
|
||||||
|
<-guard
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
var IPs []net.IP //nolint:prealloc
|
var IPs []net.IP //nolint:prealloc
|
||||||
// usually one single host in this case
|
// usually one single host in this case
|
||||||
// so no need to run in goroutines the for loop below
|
// so no need to run in goroutines the for loop below
|
||||||
@@ -115,26 +83,15 @@ func resolvePIAHostname(ctx context.Context, wg *sync.WaitGroup,
|
|||||||
}
|
}
|
||||||
IPs = append(IPs, newIPs...)
|
IPs = append(IPs, newIPs...)
|
||||||
}
|
}
|
||||||
serversCh <- models.PIAServer{
|
serversCh <- models.PIAOldServer{
|
||||||
Region: region,
|
Region: region,
|
||||||
IPs: uniqueSortedIPs(IPs),
|
IPs: uniqueSortedIPs(IPs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyPIAServers(servers []models.PIAServer) (s string) {
|
func stringifyPIAOldServers(servers []models.PIAOldServer) (s string) {
|
||||||
s = "func PIAServers() []models.PIAServer {\n"
|
s = "func PIAOldServers() []models.PIAOldServer {\n"
|
||||||
s += " return []models.PIAServer{\n"
|
s += " return []models.PIAOldServer{\n"
|
||||||
for _, server := range servers {
|
|
||||||
s += " " + server.String() + ",\n"
|
|
||||||
}
|
|
||||||
s += " }\n"
|
|
||||||
s += "}"
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyPIAOldServers(servers []models.PIAServer) (s string) {
|
|
||||||
s = "func PIAOldServers() []models.PIAServer {\n"
|
|
||||||
s += " return []models.PIAServer{\n"
|
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
s += " " + server.String() + ",\n"
|
s += " " + server.String() + ",\n"
|
||||||
}
|
}
|
||||||
99
internal/updater/piav4.go
Normal file
99
internal/updater/piav4.go
Normal 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
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetMessage returns a message for the user describing if there is a newer version
|
// GetMessage returns a message for the user describing if there is a newer version
|
||||||
@@ -30,35 +32,12 @@ func GetMessage(version, commitShort string, client *http.Client) (message strin
|
|||||||
if tagName == version {
|
if tagName == version {
|
||||||
return fmt.Sprintf("You are running the latest release %s", version), nil
|
return fmt.Sprintf("You are running the latest release %s", version), nil
|
||||||
}
|
}
|
||||||
timeSinceRelease := formatDuration(time.Since(releaseTime))
|
timeSinceRelease := logging.FormatDuration(time.Since(releaseTime))
|
||||||
return fmt.Sprintf("There is a new release %s (%s) created %s ago",
|
return fmt.Sprintf("There is a new release %s (%s) created %s ago",
|
||||||
tagName, name, timeSinceRelease),
|
tagName, name, timeSinceRelease),
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatDuration(duration time.Duration) string {
|
|
||||||
switch {
|
|
||||||
case duration < time.Minute:
|
|
||||||
seconds := int(duration.Round(time.Second).Seconds())
|
|
||||||
if seconds < 2 {
|
|
||||||
return fmt.Sprintf("%d second", seconds)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d seconds", seconds)
|
|
||||||
case duration <= time.Hour:
|
|
||||||
minutes := int(duration.Round(time.Minute).Minutes())
|
|
||||||
if minutes == 1 {
|
|
||||||
return "1 minute"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d minutes", minutes)
|
|
||||||
case duration < 48*time.Hour:
|
|
||||||
hours := int(duration.Truncate(time.Hour).Hours())
|
|
||||||
return fmt.Sprintf("%d hours", hours)
|
|
||||||
default:
|
|
||||||
days := int(duration.Truncate(time.Hour).Hours() / 24)
|
|
||||||
return fmt.Sprintf("%d days", days)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLatestRelease(client *http.Client) (tagName, name string, time time.Time, err error) {
|
func getLatestRelease(client *http.Client) (tagName, name string, time time.Time, err error) {
|
||||||
releases, err := getGithubReleases(client)
|
releases, err := getGithubReleases(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user