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
**Is this urgent?**: No
**CPU arch** or **device name**: amd64
**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
**Share your logs...**
...*careful to remove i.e. token information with PIA port forwarding*
**Share your logs... (careful to remove in example tokens)**
```log

View File

@@ -7,9 +7,11 @@ assignees:
---
**Is this urgent?**: No
**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**:
@@ -23,9 +25,7 @@ Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
That feature doesn't work
**Share your logs...**
...*careful to remove i.e. token information with PIA port forwarding*
**Share your logs... (careful to remove in example tokens)**
```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"
color: "cfe8d4"
description: ""
- name: ":cloud: Torguard"
color: "cfe8d4"
description: ""
- name: ":cloud: Vyprvpn"
color: "cfe8d4"
description: ""

View File

@@ -72,10 +72,10 @@ jobs:
echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ)
if [ "$TAG" != "$GITHUB_REF" ]; then
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
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
echo ::set-output name=version::$BRANCH
echo ::set-output name=platforms::linux/amd64

View File

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

View File

@@ -1,7 +1,7 @@
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, 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`*
@@ -37,7 +37,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
## Features
- 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
- 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
@@ -47,7 +47,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
- 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 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
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- 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.Purevpn, "purevpn", false, "Update Purevpn 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.Windscribe, "windscribe", false, "Update Windscribe servers")
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) {
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
settings.InputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
return err
}

View File

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

View File

@@ -56,7 +56,7 @@ var (
func (settings *OpenVPN) read(r reader) (err error) {
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"},
params.Default("private internet access"))
if err != nil {
@@ -130,6 +130,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
readProvider = settings.Provider.readPurevpn
case constants.Privado:
readProvider = settings.Provider.readPrivado
case constants.Torguard:
readProvider = settings.Provider.readTorguard
default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
}

View File

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

View File

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

View File

@@ -152,6 +152,24 @@ func Test_Provider_lines(t *testing.T) {
" |--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": {
settings: Provider{
Name: constants.Vyprvpn,

View File

@@ -8,7 +8,7 @@ type ServerSelection struct {
// Common
Protocol string `json:"network_protocol"`
TargetIP net.IP `json:"target_ip,omitempty"`
// TODO comments
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
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"`
Purevpn bool `json:"purevpn"`
Surfshark bool `json:"surfshark"`
Torguard bool `json:"torguard"`
Vyprvpn bool `json:"vyprvpn"`
Windscribe bool `json:"windscribe"`
// 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.Purevpn = true
settings.Surfshark = true
settings.Torguard = true
settings.Vyprvpn = true
settings.Windscribe = true
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(),
},
Pia: models.PiaServers{
Version: 3,
Timestamp: 1611877630,
Version: 4,
Timestamp: 1613480675,
Servers: PIAServers(),
},
Purevpn: models.PurevpnServers{
@@ -41,6 +41,11 @@ func GetAllServers() (allServers models.AllServers) {
Timestamp: 1612031135,
Servers: SurfsharkServers(),
},
Torguard: models.TorguardServers{
Version: 1,
Timestamp: 1613357861,
Servers: TorguardServers(),
},
Vyprvpn: models.VyprvpnServers{
Version: 1,
Timestamp: 1612031135,

View File

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

View File

@@ -15,7 +15,9 @@ func newHandler(ctx context.Context, wg *sync.WaitGroup, logger logging.Logger,
return &handler{
ctx: ctx,
wg: wg,
client: &http.Client{Timeout: httpTimeout},
client: &http.Client{
Timeout: httpTimeout,
CheckRedirect: returnRedirect},
logger: logger,
verbose: verbose,
stealth: stealth,
@@ -62,3 +64,8 @@ var hopHeaders = [...]string{ //nolint:gochecknoglobals
"Transfer-Encoding",
"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 {
Region string `json:"region"`
ServerName string `json:"server_name"`
Protocol string `json:"protocol"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
PortForward bool `json:"port_forward"`
IP net.IP `json:"ip"`
}
func (p *PIAServer) String() string {
return fmt.Sprintf("{Region: %q, ServerName: %q, Protocol: %q, PortForward: %t, IP: %s}",
p.Region, p.ServerName, p.Protocol, p.PortForward, goStringifyIP(p.IP))
return fmt.Sprintf("{Region: %q, ServerName: %q, TCP: %t, UDP: %t, PortForward: %t, IP: %s}",
p.Region, p.ServerName, p.TCP, p.UDP, p.PortForward, goStringifyIP(p.IP))
}
type MullvadServer struct {
@@ -55,6 +56,18 @@ func (s *SurfsharkServer) String() string {
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 {
Region string `json:"region"`
Group string `json:"group"`

View File

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

View File

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

View File

@@ -341,8 +341,9 @@ func filterPIAServers(servers []models.PIAServer, regions []string, protocol str
filtered []models.PIAServer) {
for _, server := range servers {
switch {
case filterByPossibilities(server.Region, regions):
case server.Protocol != protocol:
case filterByPossibilities(server.Region, regions),
protocol == constants.TCP && !server.TCP,
protocol == constants.UDP && !server.UDP:
default:
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)
case constants.Privado:
return newPrivado(allServers.Privado.Servers, timeNow)
case constants.Torguard:
return newTorguard(allServers.Torguard.Servers, timeNow)
default:
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),
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn),
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe),
}
@@ -106,6 +107,22 @@ func (s *storage) mergeSurfshark(hardcoded, persisted models.SurfsharkServers) m
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 {
if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded

View File

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

View File

@@ -10,7 +10,6 @@ import (
"sort"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
)
@@ -48,34 +47,62 @@ func (u *updater) updatePIA(ctx context.Context) (err error) {
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 _, 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{
Region: region.Name,
ServerName: udpServer.CN,
Protocol: constants.UDP,
TCP: protocols.tcp,
UDP: protocols.udp,
PortForward: region.PortForward,
IP: udpServer.IP,
}
delete(ipToProtocols, udpServer.IP.String())
servers = append(servers, server)
}
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{
Region: region.Name,
ServerName: tcpServer.CN,
Protocol: constants.UDP,
TCP: protocols.tcp,
UDP: protocols.udp,
PortForward: region.PortForward,
IP: tcpServer.IP,
}
delete(ipToProtocols, tcpServer.IP.String())
servers = append(servers, server)
}
}
sort.Slice(servers, func(i, j int) bool {
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].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) {
uniqueIPs := make(map[string]struct{})
for i := 0; i < repetition; i++ {
i := 0
for {
newIPs, err := lookupIP(ctx, host)
if err != nil {
return nil, err
@@ -95,6 +96,12 @@ func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string,
key := ip.String()
uniqueIPs[key] = struct{}{}
}
i++
if i == repetition {
break
}
timer := time.NewTimer(timeBetween)
select {
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.
func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServers, err error) { //nolint:gocognit
//nolint:gocognit,gocyclo
func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServers, err error) {
if u.options.Cyberghost {
u.logger.Info("updating Cyberghost servers...")
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 {
u.logger.Info("updating Vyprvpn servers...")
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)
if u.options.CLI {
for _, warning := range warnings {
u.logger.Warn("Privado: %s", warning)
u.logger.Warn("Vyprvpn: %s", warning)
}
}
if err != nil {