Compare commits

...

11 Commits

Author SHA1 Message Date
Quentin McGaw
1748a2ae12 Fix: HTTP proxy password and log settings reading 2021-02-26 03:32:26 +00:00
Quentin McGaw
eff46aa97a Fix firewall settings parsing, fixes #392 2021-02-21 02:39:34 +00:00
Quentin McGaw
9fb186af75 Documentation: update issue templates 2021-02-20 22:29:33 +00:00
Quentin McGaw
f1b1001863 Torguard support (#387)
See discussion on #374
2021-02-17 20:36:30 -05:00
Quentin McGaw
c5af536299 Maintenance: deduplicate PIA servers by protocols 2021-02-16 13:06:58 +00:00
Quentin McGaw
b9b2f691a5 Fix: pia updater for TCP, fixes #388 2021-02-16 13:06:51 +00:00
fgeertsema
bdc8817672 Fix: HTTP proxy: return the response of a redirect, do not follow (#384)
Authored-by: Fernand Geertsema <fernand@web-iq.eu>
2021-02-15 08:40:51 -05:00
Quentin McGaw
a55acb2816 CI: Alpine s390x build removed (periodic crashes) 2021-02-14 18:59:27 +00:00
Quentin McGaw
d686c76db3 Fix: Privado SERVER_HOSTNAME selection 2021-02-14 16:40:48 +00:00
Quentin McGaw
30c1ae651e Documentation: new provider issue template 2021-02-14 16:31:31 +00:00
Quentin McGaw
adaad62fbd Feature: updater: no sleep for last DNS resolution 2021-02-12 21:27:26 +00:00
36 changed files with 1200 additions and 675 deletions

View File

@@ -7,9 +7,11 @@ assignees: qdm12
--- ---
**Is this urgent?**: No
**Host OS** (approximate answer is fine too): Ubuntu 18 **Host OS** (approximate answer is fine too): Ubuntu 18
**Is this urgent?**: No **CPU arch** or **device name**: amd64
**What VPN provider are you using**: **What VPN provider are you using**:
@@ -25,9 +27,7 @@ Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
That feature doesn't work That feature doesn't work
**Share your logs...** **Share your logs... (careful to remove in example tokens)**
...*careful to remove i.e. token information with PIA port forwarding*
```log ```log

View File

@@ -7,9 +7,11 @@ assignees:
--- ---
**Is this urgent?**: No
**Host OS** (approximate answer is fine too): Ubuntu 18 **Host OS** (approximate answer is fine too): Ubuntu 18
**Is this urgent?**: No **CPU arch** or **device name**: amd64
**What VPN provider are you using**: **What VPN provider are you using**:
@@ -23,9 +25,7 @@ Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
That feature doesn't work That feature doesn't work
**Share your logs...** **Share your logs... (careful to remove in example tokens)**
...*careful to remove i.e. token information with PIA port forwarding*
```log ```log

16
.github/ISSUE_TEMPLATE/provider.md vendored Normal file
View File

@@ -0,0 +1,16 @@
---
name: Support a VPN provider
about: Suggest a VPN provider to be supported
title: 'VPN provider support: NAME OF THE PROVIDER'
labels: ":bulb: New provider"
---
One of the following is required:
- Publicly accessible URL to a zip file containing the Openvpn configuration files
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
If the list of servers requires to login **or** is hidden behind an interactive configurator,
it's not possible to support the provider yet. Please instead subscribe to issue #223 which would solve it.

3
.github/labels.yml vendored
View File

@@ -36,6 +36,9 @@
- name: ":cloud: Surfshark" - name: ":cloud: Surfshark"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: Torguard"
color: "cfe8d4"
description: ""
- name: ":cloud: Vyprvpn" - name: ":cloud: Vyprvpn"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -72,10 +72,10 @@ jobs:
echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ) echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ)
if [ "$TAG" != "$GITHUB_REF" ]; then if [ "$TAG" != "$GITHUB_REF" ]; then
echo ::set-output name=version::$TAG echo ::set-output name=version::$TAG
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
elif [ "$BRANCH" = "master" ]; then elif [ "$BRANCH" = "master" ]; then
echo ::set-output name=version::latest echo ::set-output name=version::latest
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
else else
echo ::set-output name=version::$BRANCH echo ::set-output name=version::$BRANCH
echo ::set-output name=platforms::linux/amd64 echo ::set-output name=platforms::linux/amd64

View File

@@ -13,6 +13,9 @@ issues:
- path: internal/server/ - path: internal/server/
linters: linters:
- dupl - dupl
- path: internal/configuration/
linters:
- dupl
linters: linters:
disable-all: true disable-all: true
enable: enable:

View File

@@ -1,7 +1,7 @@
# Gluetun VPN client # Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access, *Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN, Privado and TorGuard VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`* **ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
@@ -37,7 +37,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
## Features ## Features
- Based on Alpine 3.12 for a small Docker image of 52MB - Based on Alpine 3.12 for a small Docker image of 52MB
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN**, **PureVPN** and **Privado** servers - Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN**, **PureVPN**, **Privado** and **TorGuard** servers
- Supports Openvpn only for now - Supports Openvpn only for now
- DNS over TLS baked in with service provider(s) of your choice - DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
@@ -47,7 +47,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP) - Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun) - [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun) - [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even s390x as well as ppc64le 🎆 - Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- VPN server side port forwarding for Private Internet Access and Vyprvpn - VPN server side port forwarding for Private Internet Access and Vyprvpn
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers - Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Subprograms all drop root privileges once launched - Subprograms all drop root privileges once launched

View File

@@ -29,6 +29,7 @@ func (c *cli) Update(ctx context.Context, args []string, os os.OS) error {
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers") flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers") flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers") flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers")
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers") flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers") flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {

View File

@@ -82,7 +82,7 @@ func (settings *Firewall) readVPNInputPorts(env params.Env) (err error) {
} }
func (settings *Firewall) readInputPorts(env params.Env) (err error) { func (settings *Firewall) readInputPorts(env params.Env) (err error) {
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS") settings.InputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
return err return err
} }

View File

@@ -58,7 +58,7 @@ func (settings *HTTPProxy) read(r reader) (err error) {
return err return err
} }
settings.Password, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false, settings.Password, err = r.getFromEnvOrSecretFile("HTTPPROXY_PASSWORD", false,
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"}) []string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"})
if err != nil { if err != nil {
return err return err
@@ -95,10 +95,10 @@ func (settings *HTTPProxy) readLog(r reader) error {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "on": case "on":
settings.Enabled = true settings.Log = true
// Retro compatibility // Retro compatibility
case "info", "connect", "notice": case "info", "connect", "notice":
settings.Enabled = true settings.Log = true
} }
return nil return nil

View File

@@ -56,7 +56,7 @@ var (
func (settings *OpenVPN) read(r reader) (err error) { func (settings *OpenVPN) read(r reader) (err error) {
vpnsp, err := r.env.Inside("VPNSP", []string{ vpnsp, err := r.env.Inside("VPNSP", []string{
"pia", "private internet access", "mullvad", "windscribe", "surfshark", "pia", "private internet access", "mullvad", "windscribe", "surfshark", "torguard",
"cyberghost", "vyprvpn", "nordvpn", "purevpn", "privado"}, "cyberghost", "vyprvpn", "nordvpn", "purevpn", "privado"},
params.Default("private internet access")) params.Default("private internet access"))
if err != nil { if err != nil {
@@ -130,6 +130,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
readProvider = settings.Provider.readPurevpn readProvider = settings.Provider.readPurevpn
case constants.Privado: case constants.Privado:
readProvider = settings.Provider.readPrivado readProvider = settings.Provider.readPrivado
case constants.Torguard:
readProvider = settings.Provider.readTorguard
default: default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name) return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
} }

View File

@@ -2,7 +2,6 @@ package configuration
import ( import (
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
) )
func (settings *Provider) privadoLines() (lines []string) { func (settings *Provider) privadoLines() (lines []string) {
@@ -26,8 +25,7 @@ func (settings *Provider) readPrivado(r reader) (err error) {
return err return err
} }
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PrivadoHostnameChoices())
constants.PrivadoHostnameChoices(), params.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive))
if err != nil { if err != nil {
return err return err
} }

View File

@@ -43,6 +43,8 @@ func (settings *Provider) lines() (lines []string) {
providerLines = settings.purevpnLines() providerLines = settings.purevpnLines()
case "surfshark": case "surfshark":
providerLines = settings.surfsharkLines() providerLines = settings.surfsharkLines()
case "torguard":
providerLines = settings.torguardLines()
case "vyprvpn": case "vyprvpn":
providerLines = settings.vyprvpnLines() providerLines = settings.vyprvpnLines()
case "windscribe": case "windscribe":

View File

@@ -152,6 +152,24 @@ func Test_Provider_lines(t *testing.T) {
" |--Regions: a, b", " |--Regions: a, b",
}, },
}, },
"torguard": {
settings: Provider{
Name: constants.Torguard,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e"},
},
},
lines: []string{
"|--Torguard settings:",
" |--Network protocol: udp",
" |--Countries: a, b",
" |--Cities: c, d",
" |--Hostnames: e",
},
},
"vyprvpn": { "vyprvpn": {
settings: Provider{ settings: Provider{
Name: constants.Vyprvpn, Name: constants.Vyprvpn,

View File

@@ -8,7 +8,7 @@ type ServerSelection struct {
// Common // Common
Protocol string `json:"network_protocol"` Protocol string `json:"network_protocol"`
TargetIP net.IP `json:"target_ip,omitempty"` TargetIP net.IP `json:"target_ip,omitempty"`
// TODO comments
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN // Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
Regions []string `json:"regions"` Regions []string `json:"regions"`

View File

@@ -0,0 +1,52 @@
package configuration
import (
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) torguardLines() (lines []string) {
if len(settings.ServerSelection.Countries) > 0 {
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
}
if len(settings.ServerSelection.Cities) > 0 {
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
}
if len(settings.ServerSelection.Hostnames) > 0 {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
}
return lines
}
func (settings *Provider) readTorguard(r reader) (err error) {
settings.Name = constants.Torguard
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.TorguardCountryChoices())
if err != nil {
return err
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.TorguardCityChoices())
if err != nil {
return err
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.TorguardHostnamesChoices())
if err != nil {
return err
}
return nil
}

View File

@@ -17,6 +17,7 @@ type Updater struct {
Privado bool `json:"privado"` Privado bool `json:"privado"`
Purevpn bool `json:"purevpn"` Purevpn bool `json:"purevpn"`
Surfshark bool `json:"surfshark"` Surfshark bool `json:"surfshark"`
Torguard bool `json:"torguard"`
Vyprvpn bool `json:"vyprvpn"` Vyprvpn bool `json:"vyprvpn"`
Windscribe bool `json:"windscribe"` Windscribe bool `json:"windscribe"`
// The two below should be used in CLI mode only // The two below should be used in CLI mode only
@@ -47,6 +48,7 @@ func (settings *Updater) read(r reader) (err error) {
settings.PIA = true settings.PIA = true
settings.Purevpn = true settings.Purevpn = true
settings.Surfshark = true settings.Surfshark = true
settings.Torguard = true
settings.Vyprvpn = true settings.Vyprvpn = true
settings.Windscribe = true settings.Windscribe = true
settings.Stdout = false settings.Stdout = false

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,8 @@ func GetAllServers() (allServers models.AllServers) {
Servers: NordvpnServers(), Servers: NordvpnServers(),
}, },
Pia: models.PiaServers{ Pia: models.PiaServers{
Version: 3, Version: 4,
Timestamp: 1611877630, Timestamp: 1613480675,
Servers: PIAServers(), Servers: PIAServers(),
}, },
Purevpn: models.PurevpnServers{ Purevpn: models.PurevpnServers{
@@ -41,6 +41,11 @@ func GetAllServers() (allServers models.AllServers) {
Timestamp: 1612031135, Timestamp: 1612031135,
Servers: SurfsharkServers(), Servers: SurfsharkServers(),
}, },
Torguard: models.TorguardServers{
Version: 1,
Timestamp: 1613357861,
Servers: TorguardServers(),
},
Vyprvpn: models.VyprvpnServers{ Vyprvpn: models.VyprvpnServers{
Version: 1, Version: 1,
Timestamp: 1612031135, Timestamp: 1612031135,

View File

@@ -52,7 +52,7 @@ func Test_versions(t *testing.T) {
"Private Internet Access": { "Private Internet Access": {
model: models.PIAServer{}, model: models.PIAServer{},
version: allServers.Pia.Version, version: allServers.Pia.Version,
digest: "b90147aa", digest: "3e6066ec",
}, },
"Privado": { "Privado": {
model: models.PrivadoServer{}, model: models.PrivadoServer{},
@@ -69,6 +69,11 @@ func Test_versions(t *testing.T) {
version: allServers.Surfshark.Version, version: allServers.Surfshark.Version,
digest: "042bef64", digest: "042bef64",
}, },
"Torguard": {
model: models.TorguardServer{},
version: allServers.Torguard.Version,
digest: "752702f3",
},
"Vyprvpn": { "Vyprvpn": {
model: models.VyprvpnServer{}, model: models.VyprvpnServer{},
version: allServers.Vyprvpn.Version, version: allServers.Vyprvpn.Version,
@@ -133,7 +138,7 @@ func Test_timestamps(t *testing.T) {
"Private Internet Access": { "Private Internet Access": {
servers: allServers.Pia.Servers, servers: allServers.Pia.Servers,
timestamp: allServers.Pia.Timestamp, timestamp: allServers.Pia.Timestamp,
digest: "1d2938a1", digest: "e0f95a01",
}, },
"Purevpn": { "Purevpn": {
servers: allServers.Purevpn.Servers, servers: allServers.Purevpn.Servers,
@@ -150,6 +155,11 @@ func Test_timestamps(t *testing.T) {
timestamp: allServers.Surfshark.Timestamp, timestamp: allServers.Surfshark.Timestamp,
digest: "1a7f38bb", digest: "1a7f38bb",
}, },
"Torguard": {
servers: allServers.Torguard.Servers,
timestamp: allServers.Torguard.Timestamp,
digest: "dffab93e",
},
"Vyprvpn": { "Vyprvpn": {
servers: allServers.Vyprvpn.Servers, servers: allServers.Vyprvpn.Servers,
timestamp: allServers.Vyprvpn.Timestamp, timestamp: allServers.Vyprvpn.Timestamp,

View File

@@ -0,0 +1,101 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
TorguardCertificate = "MIIDMTCCAhmgAwIBAgIJAKnGGJK6qLqSMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCVRHLVZQTi1DQTAgFw0xOTA1MjExNDIzMTFaGA8yMDU5MDUxMTE0MjMxMVowFDESMBAGA1UEAwwJVEctVlBOLUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlv0UgPD3xVAvhhP6q1HCmeAWbH+9HPkyQ2P6qM5oHY5dntjmq8YT48FZGHWv7+s9O47v6Bv7rEc4UwQx15cc2LByivX2JwmE8JACvNfwEnZXYAPq9WU3ZgRrAGvA09ItuLqK2fQ4A7h8bFhmyxCbSzP1sSIT/zJY6ebuh5rDQSMJRMaoI0t1zorEZ7PlEmh+o0w5GPs0D0vY50UcnEzB4GOdWC9pJREwEqppWYLN7RRdG8JyIqmA59mhARCnQFUo38HWic4trxFe71jtD7YInNV7ShQtg0S0sXo36Rqfz72Jo08qqI70dNs5DN1aGNkQ/tRK9DhL5DLmTkaCw7mEFQIDAQABo4GDMIGAMB0GA1UdDgQWBBR7DcymXBp6u/jAaZOPUjUhEyhXfjBEBgNVHSMEPTA7gBR7DcymXBp6u/jAaZOPUjUhEyhXfqEYpBYwFDESMBAGA1UEAwwJVEctVlBOLUNBggkAqcYYkrqoupIwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAE79ngbdSlP7IBbfnJ+2Ju7vqt9/GyhcsYtjibp6gsMUxKlD8HuvlSGj5kNO5wiwN7XXqsjYtJfdhmzzVbXksi8Fnbnfa8GhFl4IAjLJ5cxaWOxjr6wx2AhIs+BVVARjaU7iTK91RXJnl6u7UDHTkQylBTl7wgpMeG6GjhaHfcOL1t7D2w8x23cTO+p+n53P3cBq+9TiAUORdzXJvbCxlPMDSDArsgBjC57W7dtdnZo7gTfQG77JTDFBeSwPwLF7PjBB4S6rzU/4fcYwy83XKP6zDn9tgUJDnpFb/7jJ/PbNkK4BWYJp3XytOtt66v9SEKw+v/fJ+VkjU16vE/9Q3h4="
TorguardOpenvpnStaticKeyV1 = "770e8de5fc56e0248cc7b5aab56be80d0e19cbf003c1b3ed68efbaf08613c3a1a019dac6a4b84f13a6198f73229ffc21fa512394e288f82aa2cf0180f01fb3eb1a71e00a077a20f6d7a83633f5b4f47f27e30617eaf8485dd8c722a8606d56b3c183f65da5d3c9001a8cbdb96c793d936251098b24fe52a6dd2472e98cfccbc466e63520d63ade7a0eacc36208c3142a1068236a52142fbb7b3ed83d785e12a28261bccfb3bcb62a8d2f6d18f5df5f3652e59c5627d8d9c8f7877c4d7b08e19a5c363556ba68d392be78b75152dd55ba0f74d45089e84f77f4492d886524ea6c82b9f4dd83d46528d4f5c3b51cfeaf2838d938bd0597c426b0e440434f2c451f"
)
func TorguardCountryChoices() (choices []string) {
servers := TorguardServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return choices
}
func TorguardCityChoices() (choices []string) {
servers := TorguardServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return choices
}
func TorguardHostnamesChoices() (choices []string) {
servers := TorguardServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return choices
}
//nolint:lll
// TorguardServers returns a slice of all the server information for Torguard.
func TorguardServers() []models.TorguardServer {
return []models.TorguardServer{
{Country: "Australia", City: "Sydney", Hostname: "au.torguardvpnaccess.com", IP: net.IP{168, 1, 210, 188}},
{Country: "Austria", City: "", Hostname: "aus.torguardvpnaccess.com", IP: net.IP{37, 120, 155, 18}},
{Country: "Belarus", City: "", Hostname: "bl.torguardvpnaccess.com", IP: net.IP{95, 47, 99, 12}},
{Country: "Belgium", City: "", Hostname: "bg.torguardvpnaccess.com", IP: net.IP{89, 249, 73, 250}},
{Country: "Brazil", City: "", Hostname: "br.torguardvpnaccess.com", IP: net.IP{169, 57, 191, 3}},
{Country: "Bulgaria", City: "", Hostname: "bul.torguardvpnaccess.com", IP: net.IP{82, 102, 23, 186}},
{Country: "Canada", City: "Toronto", Hostname: "ca.torguardvpnaccess.com", IP: net.IP{184, 75, 209, 250}},
{Country: "Canada", City: "Vancouver", Hostname: "vanc.ca.west.torguardvpnaccess.com", IP: net.IP{107, 181, 189, 48}},
{Country: "Chile", City: "", Hostname: "chil.torguardvpnaccess.com", IP: net.IP{37, 235, 52, 19}},
{Country: "Cyprus", City: "", Hostname: "cp.torguardvpnaccess.com", IP: net.IP{185, 173, 226, 49}},
{Country: "Czech", City: "", Hostname: "czech.torguardvpnaccess.com", IP: net.IP{185, 189, 115, 103}},
{Country: "Denmark", City: "", Hostname: "den.torguardvpnaccess.com", IP: net.IP{37, 120, 131, 29}},
{Country: "Finland", City: "", Hostname: "fin.torguardvpnaccess.com", IP: net.IP{91, 233, 116, 228}},
{Country: "France", City: "", Hostname: "fr.torguardvpnaccess.com", IP: net.IP{93, 177, 75, 90}},
{Country: "Germany", City: "", Hostname: "gr.torguardvpnaccess.com", IP: net.IP{93, 177, 73, 90}},
{Country: "Greece", City: "", Hostname: "gre.torguardvpnaccess.com", IP: net.IP{45, 92, 33, 10}},
{Country: "Hong", City: "Kong", Hostname: "hk.torguardvpnaccess.com", IP: net.IP{45, 133, 181, 158}},
{Country: "Hungary", City: "", Hostname: "hg.torguardvpnaccess.com", IP: net.IP{37, 120, 144, 106}},
{Country: "Iceland", City: "", Hostname: "ice.torguardvpnaccess.com", IP: net.IP{82, 221, 111, 11}},
{Country: "India", City: "Bangalore", Hostname: "in.torguardvpnaccess.com", IP: net.IP{139, 59, 62, 109}},
{Country: "Ireland", City: "", Hostname: "ire.torguardvpnaccess.com", IP: net.IP{81, 17, 246, 108}},
{Country: "Israel", City: "", Hostname: "isr.torguardvpnaccess.com", IP: net.IP{91, 223, 106, 201}},
{Country: "Italy", City: "", Hostname: "it.torguardvpnaccess.com", IP: net.IP{192, 145, 127, 190}},
{Country: "Japan", City: "", Hostname: "jp.torguardvpnaccess.com", IP: net.IP{91, 207, 174, 50}},
{Country: "Latvia", City: "", Hostname: "lv.torguardvpnaccess.com", IP: net.IP{109, 248, 149, 167}},
{Country: "Luxembourg", City: "", Hostname: "lux.torguardvpnaccess.com", IP: net.IP{94, 242, 238, 66}},
{Country: "Mexico", City: "", Hostname: "mx.torguardvpnaccess.com", IP: net.IP{45, 133, 180, 34}},
{Country: "Moldova", City: "", Hostname: "md.torguardvpnaccess.com", IP: net.IP{178, 175, 128, 74}},
{Country: "Netherlands", City: "", Hostname: "nl.torguardvpnaccess.com", IP: net.IP{88, 202, 177, 181}},
{Country: "New", City: "Zealand", Hostname: "nz.torguardvpnaccess.com", IP: net.IP{103, 108, 94, 58}},
{Country: "Norway", City: "", Hostname: "no.torguardvpnaccess.com", IP: net.IP{185, 125, 169, 31}},
{Country: "Poland", City: "", Hostname: "pl.torguardvpnaccess.com", IP: net.IP{37, 120, 156, 194}},
{Country: "Portugal", City: "", Hostname: "por.torguardvpnaccess.com", IP: net.IP{94, 46, 179, 75}},
{Country: "Romania", City: "", Hostname: "ro.torguardvpnaccess.com", IP: net.IP{93, 120, 27, 162}},
{Country: "Singapore", City: "", Hostname: "singp.torguardvpnaccess.com", IP: net.IP{206, 189, 43, 152}},
{Country: "Slovakia", City: "", Hostname: "slk.torguardvpnaccess.com", IP: net.IP{46, 29, 2, 113}},
{Country: "South", City: "Korea", Hostname: "sk.torguardvpnaccess.com", IP: net.IP{169, 56, 83, 216}},
{Country: "Spain", City: "", Hostname: "sp.torguardvpnaccess.com", IP: net.IP{192, 145, 124, 242}},
{Country: "Sweden", City: "", Hostname: "swe.torguardvpnaccess.com", IP: net.IP{37, 120, 153, 72}},
{Country: "Switzerland", City: "", Hostname: "swiss.torguardvpnaccess.com", IP: net.IP{195, 206, 105, 37}},
{Country: "Taiwan", City: "", Hostname: "tw.torguardvpnaccess.com", IP: net.IP{61, 216, 159, 176}},
{Country: "Thailand", City: "", Hostname: "thai.torguardvpnaccess.com", IP: net.IP{202, 129, 16, 104}},
{Country: "UAE", City: "", Hostname: "uae.secureconnect.me", IP: net.IP{45, 9, 250, 10}},
{Country: "UK", City: "London", Hostname: "uk.torguardvpnaccess.com", IP: net.IP{109, 123, 118, 13}},
{Country: "USA", City: "Atlanta", Hostname: "atl.east.usa.torguardvpnaccess.com", IP: net.IP{104, 223, 95, 50}},
{Country: "USA", City: "Chicago", Hostname: "chi.central.usa.torguardvpnaccess.com", IP: net.IP{167, 160, 172, 106}},
{Country: "USA", City: "Dallas", Hostname: "dal.central.usa.torguardvpnaccess.com", IP: net.IP{96, 44, 145, 26}},
{Country: "USA", City: "Las Vegas", Hostname: "lv.west.usa.torguardvpnaccess.com", IP: net.IP{76, 164, 203, 130}},
{Country: "USA", City: "Los Angeles", Hostname: "la.west.usa.torguardvpnaccess.com", IP: net.IP{67, 215, 236, 58}},
{Country: "USA", City: "Miami", Hostname: "fl.east.usa.torguardvpnaccess.com", IP: net.IP{96, 47, 226, 42}},
{Country: "USA", City: "New Jersey", Hostname: "nj.east.usa.torguardvpnaccess.com", IP: net.IP{23, 226, 128, 146}},
{Country: "USA", City: "New York", Hostname: "ny.east.usa.torguardvpnaccess.com", IP: net.IP{209, 95, 50, 116}},
{Country: "USA", City: "San Francisco", Hostname: "sf.west.usa.torguardvpnaccess.com", IP: net.IP{206, 189, 218, 238}},
{Country: "USA", City: "Seattle", Hostname: "sa.west.usa.torguardvpnaccess.com", IP: net.IP{199, 229, 250, 38}},
}
}

View File

@@ -19,6 +19,8 @@ const (
Purevpn = "purevpn" Purevpn = "purevpn"
// Privado is a VPN provider. // Privado is a VPN provider.
Privado = "privado" Privado = "privado"
// Torguard is a VPN provider.
Torguard = "torguard"
) )
const ( const (

View File

@@ -15,7 +15,9 @@ func newHandler(ctx context.Context, wg *sync.WaitGroup, logger logging.Logger,
return &handler{ return &handler{
ctx: ctx, ctx: ctx,
wg: wg, wg: wg,
client: &http.Client{Timeout: httpTimeout}, client: &http.Client{
Timeout: httpTimeout,
CheckRedirect: returnRedirect},
logger: logger, logger: logger,
verbose: verbose, verbose: verbose,
stealth: stealth, stealth: stealth,
@@ -62,3 +64,8 @@ var hopHeaders = [...]string{ //nolint:gochecknoglobals
"Transfer-Encoding", "Transfer-Encoding",
"Upgrade", "Upgrade",
} }
// Do not follow redirect, but directly return the redirect response.
func returnRedirect(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}

View File

@@ -10,14 +10,15 @@ import (
type PIAServer struct { type PIAServer struct {
Region string `json:"region"` Region string `json:"region"`
ServerName string `json:"server_name"` ServerName string `json:"server_name"`
Protocol string `json:"protocol"` TCP bool `json:"tcp"`
UDP bool `json:"udp"`
PortForward bool `json:"port_forward"` PortForward bool `json:"port_forward"`
IP net.IP `json:"ip"` IP net.IP `json:"ip"`
} }
func (p *PIAServer) String() string { func (p *PIAServer) String() string {
return fmt.Sprintf("{Region: %q, ServerName: %q, Protocol: %q, PortForward: %t, IP: %s}", return fmt.Sprintf("{Region: %q, ServerName: %q, TCP: %t, UDP: %t, PortForward: %t, IP: %s}",
p.Region, p.ServerName, p.Protocol, p.PortForward, goStringifyIP(p.IP)) p.Region, p.ServerName, p.TCP, p.UDP, p.PortForward, goStringifyIP(p.IP))
} }
type MullvadServer struct { type MullvadServer struct {
@@ -55,6 +56,18 @@ func (s *SurfsharkServer) String() string {
return fmt.Sprintf("{Region: %q, IPs: %s}", s.Region, goStringifyIPs(s.IPs)) return fmt.Sprintf("{Region: %q, IPs: %s}", s.Region, goStringifyIPs(s.IPs))
} }
type TorguardServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
IP net.IP `json:"ip"`
}
func (s *TorguardServer) String() string {
return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, IP: %s}",
s.Country, s.City, s.Hostname, goStringifyIP(s.IP))
}
type CyberghostServer struct { type CyberghostServer struct {
Region string `json:"region"` Region string `json:"region"`
Group string `json:"group"` Group string `json:"group"`

View File

@@ -9,6 +9,7 @@ type AllServers struct {
Privado PrivadoServers `json:"privado"` Privado PrivadoServers `json:"privado"`
Purevpn PurevpnServers `json:"purevpn"` Purevpn PurevpnServers `json:"purevpn"`
Surfshark SurfsharkServers `json:"surfshark"` Surfshark SurfsharkServers `json:"surfshark"`
Torguard TorguardServers `json:"torguard"`
Vyprvpn VyprvpnServers `json:"vyprvpn"` Vyprvpn VyprvpnServers `json:"vyprvpn"`
Windscribe WindscribeServers `json:"windscribe"` Windscribe WindscribeServers `json:"windscribe"`
} }
@@ -21,6 +22,7 @@ func (a *AllServers) Count() int {
len(a.Privado.Servers) + len(a.Privado.Servers) +
len(a.Purevpn.Servers) + len(a.Purevpn.Servers) +
len(a.Surfshark.Servers) + len(a.Surfshark.Servers) +
len(a.Torguard.Servers) +
len(a.Vyprvpn.Servers) + len(a.Vyprvpn.Servers) +
len(a.Windscribe.Servers) len(a.Windscribe.Servers)
} }
@@ -60,6 +62,11 @@ type SurfsharkServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []SurfsharkServer `json:"servers"` Servers []SurfsharkServer `json:"servers"`
} }
type TorguardServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []TorguardServer `json:"servers"`
}
type VyprvpnServers struct { type VyprvpnServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -2,5 +2,6 @@ package provider
const ( const (
aes256cbc = "aes-256-cbc" aes256cbc = "aes-256-cbc"
aes256gcm = "aes-256-gcm"
sha256 = "sha256" sha256 = "sha256"
) )

View File

@@ -341,8 +341,9 @@ func filterPIAServers(servers []models.PIAServer, regions []string, protocol str
filtered []models.PIAServer) { filtered []models.PIAServer) {
for _, server := range servers { for _, server := range servers {
switch { switch {
case filterByPossibilities(server.Region, regions): case filterByPossibilities(server.Region, regions),
case server.Protocol != protocol: protocol == constants.TCP && !server.TCP,
protocol == constants.UDP && !server.UDP:
default: default:
filtered = append(filtered, server) filtered = append(filtered, server)
} }

View File

@@ -43,6 +43,8 @@ func New(provider string, allServers models.AllServers, timeNow timeNowFunc) Pro
return newPurevpn(allServers.Purevpn.Servers, timeNow) return newPurevpn(allServers.Purevpn.Servers, timeNow)
case constants.Privado: case constants.Privado:
return newPrivado(allServers.Privado.Servers, timeNow) return newPrivado(allServers.Privado.Servers, timeNow)
case constants.Torguard:
return newTorguard(allServers.Torguard.Servers, timeNow)
default: default:
return nil // should never occur return nil // should never occur
} }

View File

@@ -0,0 +1,173 @@
package provider
import (
"context"
"fmt"
"math/rand"
"net"
"net/http"
"strconv"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
)
type torguard struct {
servers []models.TorguardServer
randSource rand.Source
}
func newTorguard(servers []models.TorguardServer, timeNow timeNowFunc) *torguard {
return &torguard{
servers: servers,
randSource: rand.NewSource(timeNow().UnixNano()),
}
}
func (t *torguard) filterServers(countries, cities, hostnames []string) (servers []models.TorguardServer) {
for _, server := range t.servers {
switch {
case filterByPossibilities(server.Country, countries):
case filterByPossibilities(server.City, cities):
case filterByPossibilities(server.Hostname, hostnames):
default:
servers = append(servers, server)
}
}
return servers
}
func (t *torguard) notFoundErr(selection configuration.ServerSelection) error {
message := "no server found for protocol " + selection.Protocol
if len(selection.Countries) > 0 {
message += " + countries " + commaJoin(selection.Countries)
}
if len(selection.Cities) > 0 {
message += " + cities " + commaJoin(selection.Cities)
}
if len(selection.Hostnames) > 0 {
message += " + hostnames " + commaJoin(selection.Hostnames)
}
return fmt.Errorf(message)
}
func (t *torguard) GetOpenVPNConnection(selection configuration.ServerSelection) (
connection models.OpenVPNConnection, err error) {
var port uint16 = 1912
if selection.CustomPort > 0 {
port = selection.CustomPort
}
if selection.TargetIP != nil {
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
}
servers := t.filterServers(selection.Countries, selection.Cities, selection.Hostnames)
if len(servers) == 0 {
return connection, t.notFoundErr(selection)
}
connections := make([]models.OpenVPNConnection, len(servers))
for i := range servers {
connections[i] = models.OpenVPNConnection{
IP: servers[i].IP,
Port: port,
Protocol: selection.Protocol,
}
}
return pickRandomConnection(connections, t.randSource), nil
}
func (t *torguard) BuildConf(connection models.OpenVPNConnection,
username string, settings configuration.OpenVPN) (lines []string) {
if len(settings.Cipher) == 0 {
settings.Cipher = aes256gcm
}
if len(settings.Auth) == 0 {
settings.Auth = sha256
}
const defaultMSSFix = 1450
if settings.MSSFix == 0 {
settings.MSSFix = defaultMSSFix
}
lines = []string{
"client",
"dev tun",
"nobind",
"persist-key",
"remote-cert-tls server",
"tls-exit",
// Torguard specific
"tun-mtu 1500",
"tun-mtu-extra 32",
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
"reneg-sec 0",
"fast-io",
"key-direction 1",
"script-security 2",
"ncp-disable",
"compress",
"keepalive 5 30",
"sndbuf 393216",
"rcvbuf 393216",
// "up /etc/openvpn/update-resolv-conf",
// "down /etc/openvpn/update-resolv-conf",
// Added constant values
"auth-nocache",
"mute-replay-warnings",
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
"pull-filter ignore \"block-outside-dns\"",
"auth-retry nointeract",
"suppress-timestamps",
// Modified variables
"verb " + strconv.Itoa(settings.Verbosity),
"auth-user-pass " + constants.OpenVPNAuthConf,
"proto " + connection.Protocol,
"remote " + connection.IP.String() + " " + strconv.Itoa(int(connection.Port)),
"cipher " + settings.Cipher,
"auth " + settings.Auth,
}
if !settings.Root {
lines = append(lines, "user "+username)
}
lines = append(lines, []string{
"<ca>",
"-----BEGIN CERTIFICATE-----",
constants.TorguardCertificate,
"-----END CERTIFICATE-----",
"</ca>",
}...)
lines = append(lines, []string{
"<tls-auth>",
"-----BEGIN OpenVPN Static key V1-----",
constants.TorguardOpenvpnStaticKeyV1,
"-----END OpenVPN Static key V1-----",
"</tls-auth>",
"",
}...)
return lines
}
func (t *torguard) PortForward(ctx context.Context, client *http.Client,
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
syncState func(port uint16) (pfFilepath string)) {
panic("port forwarding is not supported for torguard")
}

View File

@@ -24,6 +24,7 @@ func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn), Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark), Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn), Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn),
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe), Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe),
} }
@@ -106,6 +107,22 @@ func (s *storage) mergeSurfshark(hardcoded, persisted models.SurfsharkServers) m
return persisted return persisted
} }
func (s *storage) mergeTorguard(hardcoded, persisted models.TorguardServers) models.TorguardServers {
if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded
}
versionDiff := hardcoded.Version - persisted.Version
if versionDiff > 0 {
s.logger.Info(
"Torguard servers from file discarded because they are %d versions behind",
versionDiff)
return hardcoded
}
s.logger.Info("Using Torguard servers from file (%s more recent)",
getUnixTimeDifference(persisted.Timestamp, hardcoded.Timestamp))
return persisted
}
func (s *storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) models.VyprvpnServers { func (s *storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) models.VyprvpnServers {
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded return hardcoded

View File

@@ -24,6 +24,7 @@ func countServers(allServers models.AllServers) int {
len(allServers.Privado.Servers) + len(allServers.Privado.Servers) +
len(allServers.Purevpn.Servers) + len(allServers.Purevpn.Servers) +
len(allServers.Surfshark.Servers) + len(allServers.Surfshark.Servers) +
len(allServers.Torguard.Servers) +
len(allServers.Vyprvpn.Servers) + len(allServers.Vyprvpn.Servers) +
len(allServers.Windscribe.Servers) len(allServers.Windscribe.Servers)
} }

View File

@@ -10,7 +10,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
) )
@@ -48,34 +47,62 @@ func (u *updater) updatePIA(ctx context.Context) (err error) {
return err return err
} }
servers := make([]models.PIAServer, 0, len(data.Regions)) // Deduplicate servers with the same IP address
type NetProtocols struct {
tcp, udp bool
}
ipToProtocols := make(map[string]NetProtocols)
for _, region := range data.Regions { for _, region := range data.Regions {
for _, udpServer := range region.Servers.UDP { for _, udpServer := range region.Servers.UDP {
protocols := ipToProtocols[udpServer.IP.String()]
protocols.udp = true
ipToProtocols[udpServer.IP.String()] = protocols
}
for _, tcpServer := range region.Servers.TCP {
protocols := ipToProtocols[tcpServer.IP.String()]
protocols.tcp = true
ipToProtocols[tcpServer.IP.String()] = protocols
}
}
servers := make([]models.PIAServer, 0, len(ipToProtocols)) // set the capacity, not the length of the slice
for _, region := range data.Regions {
for _, udpServer := range region.Servers.UDP {
protocols, ok := ipToProtocols[udpServer.IP.String()]
if !ok { // already added that IP for a server
continue
}
server := models.PIAServer{ server := models.PIAServer{
Region: region.Name, Region: region.Name,
ServerName: udpServer.CN, ServerName: udpServer.CN,
Protocol: constants.UDP, TCP: protocols.tcp,
UDP: protocols.udp,
PortForward: region.PortForward, PortForward: region.PortForward,
IP: udpServer.IP, IP: udpServer.IP,
} }
delete(ipToProtocols, udpServer.IP.String())
servers = append(servers, server) servers = append(servers, server)
} }
for _, tcpServer := range region.Servers.TCP { for _, tcpServer := range region.Servers.TCP {
protocols, ok := ipToProtocols[tcpServer.IP.String()]
if !ok { // already added that IP for a server
continue
}
server := models.PIAServer{ server := models.PIAServer{
Region: region.Name, Region: region.Name,
ServerName: tcpServer.CN, ServerName: tcpServer.CN,
Protocol: constants.UDP, TCP: protocols.tcp,
UDP: protocols.udp,
PortForward: region.PortForward, PortForward: region.PortForward,
IP: tcpServer.IP, IP: tcpServer.IP,
} }
delete(ipToProtocols, tcpServer.IP.String())
servers = append(servers, server) servers = append(servers, server)
} }
} }
sort.Slice(servers, func(i, j int) bool { sort.Slice(servers, func(i, j int) bool {
if servers[i].Region == servers[j].Region { if servers[i].Region == servers[j].Region {
if servers[i].ServerName == servers[j].ServerName {
return servers[i].Protocol < servers[j].Protocol
}
return servers[i].ServerName < servers[j].ServerName return servers[i].ServerName < servers[j].ServerName
} }
return servers[i].Region < servers[j].Region return servers[i].Region < servers[j].Region

View File

@@ -86,7 +86,8 @@ func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string,
repetition int, timeBetween time.Duration) (ips []net.IP, err error) { repetition int, timeBetween time.Duration) (ips []net.IP, err error) {
uniqueIPs := make(map[string]struct{}) uniqueIPs := make(map[string]struct{})
for i := 0; i < repetition; i++ { i := 0
for {
newIPs, err := lookupIP(ctx, host) newIPs, err := lookupIP(ctx, host)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -95,6 +96,12 @@ func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string,
key := ip.String() key := ip.String()
uniqueIPs[key] = struct{}{} uniqueIPs[key] = struct{}{}
} }
i++
if i == repetition {
break
}
timer := time.NewTimer(timeBetween) timer := time.NewTimer(timeBetween)
select { select {
case <-timer.C: case <-timer.C:

View File

@@ -0,0 +1,114 @@
package updater
import (
"context"
"fmt"
"net"
"sort"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
)
func (u *updater) updateTorguard(ctx context.Context) (err error) {
servers, warnings, err := findTorguardServersFromZip(ctx, u.client)
if u.options.CLI {
for _, warning := range warnings {
u.logger.Warn("Torguard: %s", warning)
}
}
if err != nil {
return fmt.Errorf("cannot update Torguard servers: %w", err)
}
if u.options.Stdout {
u.println(stringifyTorguardServers(servers))
}
u.servers.Torguard.Timestamp = u.timeNow().Unix()
u.servers.Torguard.Servers = servers
return nil
}
func findTorguardServersFromZip(ctx context.Context, client network.Client) (
servers []models.TorguardServer, warnings []string, err error) {
// Note: all servers do both TCP and UDP
const zipURL = "https://torguard.net/downloads/OpenVPN-TCP-Linux.zip"
contents, err := fetchAndExtractFiles(ctx, client, zipURL)
if err != nil {
return nil, nil, err
}
for fileName, content := range contents {
var server models.TorguardServer
const prefix = "TorGuard."
const suffix = ".ovpn"
s := strings.TrimPrefix(fileName, prefix)
s = strings.TrimSuffix(s, suffix)
switch {
case strings.Count(s, ".") == 1 && !strings.HasPrefix(s, "USA"):
parts := strings.Split(s, ".")
server.Country = parts[0]
server.City = parts[1]
case strings.HasPrefix(s, "USA"):
server.Country = "USA"
s = strings.TrimPrefix(s, "USA-")
s = strings.ReplaceAll(s, "-", " ")
s = strings.ReplaceAll(s, ".", " ")
s = strings.ToLower(s)
s = strings.Title(s)
server.City = s
default:
server.Country = s
}
hostnames := extractRemoteHostsFromOpenvpn(content, true, false)
if len(hostnames) != 1 {
warning := "found " + strconv.Itoa(len(hostnames)) +
" hostname(s) instead of 1 in " + fileName
warnings = append(warnings, warning)
continue
}
server.Hostname = hostnames[0]
IPs := extractRemoteHostsFromOpenvpn(content, false, true)
if len(IPs) != 1 {
warning := "found " + strconv.Itoa(len(IPs)) +
" IP(s) instead of 1 in " + fileName
warnings = append(warnings, warning)
continue
}
server.IP = net.ParseIP(IPs[0])
if server.IP == nil {
warnings = append(warnings, "IP address "+IPs[0]+" is not valid in file "+fileName)
}
servers = append(servers, server)
}
sort.Slice(servers, func(i, j int) bool {
if servers[i].Country == servers[j].Country {
if servers[i].City == servers[j].City {
return servers[i].Hostname < servers[j].Hostname
}
return servers[i].City < servers[j].City
}
return servers[i].Country < servers[j].Country
})
return servers, warnings, nil
}
func stringifyTorguardServers(servers []models.TorguardServer) (s string) {
s = "func TorguardServers() []models.TorguardServer {\n"
s += " return []models.TorguardServer{\n"
for _, server := range servers {
s += " " + server.String() + ",\n"
}
s += " }\n"
s += "}"
return s
}

View File

@@ -50,8 +50,8 @@ func New(settings configuration.Updater, httpClient *http.Client,
} }
} }
// TODO parallelize DNS resolution. //nolint:gocognit,gocyclo
func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServers, err error) { //nolint:gocognit func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServers, err error) {
if u.options.Cyberghost { if u.options.Cyberghost {
u.logger.Info("updating Cyberghost servers...") u.logger.Info("updating Cyberghost servers...")
if err := u.updateCyberghost(ctx); err != nil { if err := u.updateCyberghost(ctx); err != nil {
@@ -124,6 +124,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
} }
} }
if u.options.Torguard {
u.logger.Info("updating Torguard servers...")
if err := u.updateTorguard(ctx); err != nil {
if ctxErr := ctx.Err(); ctxErr != nil {
return allServers, ctxErr
}
u.logger.Error(err)
}
}
if u.options.Vyprvpn { if u.options.Vyprvpn {
u.logger.Info("updating Vyprvpn servers...") u.logger.Info("updating Vyprvpn servers...")
if err := u.updateVyprvpn(ctx); err != nil { if err := u.updateVyprvpn(ctx); err != nil {

View File

@@ -14,7 +14,7 @@ func (u *updater) updateVyprvpn(ctx context.Context) (err error) {
servers, warnings, err := findVyprvpnServers(ctx, u.client, u.lookupIP) servers, warnings, err := findVyprvpnServers(ctx, u.client, u.lookupIP)
if u.options.CLI { if u.options.CLI {
for _, warning := range warnings { for _, warning := range warnings {
u.logger.Warn("Privado: %s", warning) u.logger.Warn("Vyprvpn: %s", warning)
} }
} }
if err != nil { if err != nil {