Torguard support (#387)

See discussion on #374
This commit is contained in:
Quentin McGaw
2021-02-17 20:36:30 -05:00
committed by GitHub
parent c5af536299
commit f1b1001863
24 changed files with 545 additions and 7 deletions

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

@@ -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

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

@@ -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

@@ -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

View File

@@ -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

@@ -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,
@@ -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

@@ -56,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

@@ -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

@@ -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 {