Feature: IVPN support
This commit is contained in:
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@@ -21,6 +21,9 @@
|
|||||||
- name: ":cloud: HideMyAss"
|
- name: ":cloud: HideMyAss"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: IVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: FastestVPN"
|
- name: ":cloud: FastestVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
|
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, FastestVPN,
|
||||||
HideMyAss, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
HideMyAss, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
||||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers
|
ProtonVPN, PureVPN, Surfshark, TorGuard, VyprVPN and Windscribe VPN servers
|
||||||
using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.13 for a small Docker image of 52MB
|
- Based on Alpine 3.13 for a small Docker image of 52MB
|
||||||
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe** servers
|
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **Vyprvpn**, **Windscribe** 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
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func (c *cli) Update(ctx context.Context, args []string, os os.OS, logger loggin
|
|||||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
||||||
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
|
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
|
||||||
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
|
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
|
||||||
|
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers")
|
||||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
||||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
||||||
|
|||||||
52
internal/configuration/ivpn.go
Normal file
52
internal/configuration/ivpn.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (settings *Provider) ivpnLines() (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) readIvpn(r reader) (err error) {
|
||||||
|
settings.Name = constants.Ivpn
|
||||||
|
|
||||||
|
settings.ServerSelection.TCP, 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.IvpnCountryChoices())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IvpnCityChoices())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
192
internal/configuration/ivpn_test.go
Normal file
192
internal/configuration/ivpn_test.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/golibs/params/mock_params"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Provider_ivpnLines(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
settings Provider
|
||||||
|
lines []string
|
||||||
|
}{
|
||||||
|
"empty settings": {},
|
||||||
|
"full settings": {
|
||||||
|
settings: Provider{
|
||||||
|
ServerSelection: ServerSelection{
|
||||||
|
Countries: []string{"A", "B"},
|
||||||
|
Cities: []string{"C", "D"},
|
||||||
|
Hostnames: []string{"E", "F"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"|--Countries: A, B",
|
||||||
|
"|--Cities: C, D",
|
||||||
|
"|--Hostnames: E, F",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
lines := testCase.settings.ivpnLines()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.lines, lines)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Provider_readIvpn(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var errDummy = errors.New("dummy test error")
|
||||||
|
|
||||||
|
type singleStringCall struct {
|
||||||
|
call bool
|
||||||
|
value string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceStringCall struct {
|
||||||
|
call bool
|
||||||
|
values []string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
protocol singleStringCall
|
||||||
|
targetIP singleStringCall
|
||||||
|
countries sliceStringCall
|
||||||
|
cities sliceStringCall
|
||||||
|
hostnames sliceStringCall
|
||||||
|
settings Provider
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"protocol error": {
|
||||||
|
protocol: singleStringCall{call: true, err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"target IP error": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"countries error": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true},
|
||||||
|
countries: sliceStringCall{call: true, err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"cities error": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true},
|
||||||
|
countries: sliceStringCall{call: true},
|
||||||
|
cities: sliceStringCall{call: true, err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"hostnames error": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true},
|
||||||
|
countries: sliceStringCall{call: true},
|
||||||
|
cities: sliceStringCall{call: true},
|
||||||
|
hostnames: sliceStringCall{call: true, err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"default settings": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true},
|
||||||
|
countries: sliceStringCall{call: true},
|
||||||
|
cities: sliceStringCall{call: true},
|
||||||
|
hostnames: sliceStringCall{call: true},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"set settings": {
|
||||||
|
protocol: singleStringCall{call: true, value: constants.TCP},
|
||||||
|
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
|
||||||
|
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
|
||||||
|
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
|
||||||
|
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
ServerSelection: ServerSelection{
|
||||||
|
TCP: true,
|
||||||
|
TargetIP: net.IPv4(1, 2, 3, 4),
|
||||||
|
Countries: []string{"A", "B"},
|
||||||
|
Cities: []string{"C", "D"},
|
||||||
|
Hostnames: []string{"E", "F"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
env := mock_params.NewMockEnv(ctrl)
|
||||||
|
if testCase.protocol.call {
|
||||||
|
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||||
|
Return(testCase.protocol.value, testCase.protocol.err)
|
||||||
|
}
|
||||||
|
if testCase.targetIP.call {
|
||||||
|
env.EXPECT().Get("OPENVPN_TARGET_IP").
|
||||||
|
Return(testCase.targetIP.value, testCase.targetIP.err)
|
||||||
|
}
|
||||||
|
if testCase.countries.call {
|
||||||
|
env.EXPECT().CSVInside("COUNTRY", constants.IvpnCountryChoices()).
|
||||||
|
Return(testCase.countries.values, testCase.countries.err)
|
||||||
|
}
|
||||||
|
if testCase.cities.call {
|
||||||
|
env.EXPECT().CSVInside("CITY", constants.IvpnCityChoices()).
|
||||||
|
Return(testCase.cities.values, testCase.cities.err)
|
||||||
|
}
|
||||||
|
if testCase.hostnames.call {
|
||||||
|
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices()).
|
||||||
|
Return(testCase.hostnames.values, testCase.hostnames.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reader{env: env}
|
||||||
|
|
||||||
|
var settings Provider
|
||||||
|
err := settings.readIvpn(r)
|
||||||
|
|
||||||
|
if testCase.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.settings, settings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,7 +62,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{
|
||||||
"cyberghost", "fastestvpn", "hidemyass", "mullvad", "nordvpn",
|
"cyberghost", "fastestvpn", "hidemyass", "ivpn", "mullvad", "nordvpn",
|
||||||
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||||
"purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"},
|
"purevpn", "surfshark", "torguard", "vyprvpn", "windscribe"},
|
||||||
params.Default("private internet access"))
|
params.Default("private internet access"))
|
||||||
@@ -132,6 +132,8 @@ func (settings *OpenVPN) read(r reader) (err error) {
|
|||||||
readProvider = settings.Provider.readFastestvpn
|
readProvider = settings.Provider.readFastestvpn
|
||||||
case constants.HideMyAss:
|
case constants.HideMyAss:
|
||||||
readProvider = settings.Provider.readHideMyAss
|
readProvider = settings.Provider.readHideMyAss
|
||||||
|
case constants.Ivpn:
|
||||||
|
readProvider = settings.Provider.readIvpn
|
||||||
case constants.Mullvad:
|
case constants.Mullvad:
|
||||||
readProvider = settings.Provider.readMullvad
|
readProvider = settings.Provider.readMullvad
|
||||||
case constants.Nordvpn:
|
case constants.Nordvpn:
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ func (settings *Provider) lines() (lines []string) {
|
|||||||
providerLines = settings.fastestvpnLines()
|
providerLines = settings.fastestvpnLines()
|
||||||
case "hidemyass":
|
case "hidemyass":
|
||||||
providerLines = settings.hideMyAssLines()
|
providerLines = settings.hideMyAssLines()
|
||||||
|
case "ivpn":
|
||||||
|
providerLines = settings.ivpnLines()
|
||||||
case "mullvad":
|
case "mullvad":
|
||||||
providerLines = settings.mullvadLines()
|
providerLines = settings.mullvadLines()
|
||||||
case "nordvpn":
|
case "nordvpn":
|
||||||
|
|||||||
@@ -73,6 +73,23 @@ func Test_Provider_lines(t *testing.T) {
|
|||||||
" |--Hostnames: e, f",
|
" |--Hostnames: e, f",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"ivpn": {
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ivpn,
|
||||||
|
ServerSelection: ServerSelection{
|
||||||
|
Countries: []string{"a", "b"},
|
||||||
|
Cities: []string{"c", "d"},
|
||||||
|
Hostnames: []string{"e", "f"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"|--Ivpn settings:",
|
||||||
|
" |--Network protocol: udp",
|
||||||
|
" |--Countries: a, b",
|
||||||
|
" |--Cities: c, d",
|
||||||
|
" |--Hostnames: e, f",
|
||||||
|
},
|
||||||
|
},
|
||||||
"mullvad": {
|
"mullvad": {
|
||||||
settings: Provider{
|
settings: Provider{
|
||||||
Name: constants.Mullvad,
|
Name: constants.Mullvad,
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
// Cyberghost
|
// Cyberghost
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
|
|
||||||
Countries []string `json:"countries"` // Fastestvpn, HideMyAss, Mullvad, PrivateVPN, Protonvpn, PureVPN
|
Countries []string `json:"countries"` // Fastestvpn, HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN
|
||||||
Cities []string `json:"cities"` // HideMyAss, Mullvad, PrivateVPN, Protonvpn, PureVPN, Windscribe
|
Cities []string `json:"cities"` // HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, Windscribe
|
||||||
Hostnames []string `json:"hostnames"` // Fastestvpn, HideMyAss, PrivateVPN, Windscribe, Privado, Protonvpn
|
Hostnames []string `json:"hostnames"` // Fastestvpn, HideMyAss, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn
|
||||||
Names []string `json:"names"` // Protonvpn
|
Names []string `json:"names"` // Protonvpn
|
||||||
|
|
||||||
// Mullvad
|
// Mullvad
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Updater struct {
|
|||||||
Cyberghost bool `json:"cyberghost"`
|
Cyberghost bool `json:"cyberghost"`
|
||||||
Fastestvpn bool `json:"fastestvpn"`
|
Fastestvpn bool `json:"fastestvpn"`
|
||||||
HideMyAss bool `json:"hidemyass"`
|
HideMyAss bool `json:"hidemyass"`
|
||||||
|
Ivpn bool `json:"ivpn"`
|
||||||
Mullvad bool `json:"mullvad"`
|
Mullvad bool `json:"mullvad"`
|
||||||
Nordvpn bool `json:"nordvpn"`
|
Nordvpn bool `json:"nordvpn"`
|
||||||
PIA bool `json:"pia"`
|
PIA bool `json:"pia"`
|
||||||
@@ -48,6 +49,7 @@ func (settings *Updater) lines() (lines []string) {
|
|||||||
func (settings *Updater) read(r reader) (err error) {
|
func (settings *Updater) read(r reader) (err error) {
|
||||||
settings.Cyberghost = true
|
settings.Cyberghost = true
|
||||||
settings.HideMyAss = true
|
settings.HideMyAss = true
|
||||||
|
settings.Ivpn = true
|
||||||
settings.Mullvad = true
|
settings.Mullvad = true
|
||||||
settings.Nordvpn = true
|
settings.Nordvpn = true
|
||||||
settings.Privado = true
|
settings.Privado = true
|
||||||
|
|||||||
90
internal/constants/ivpn.go
Normal file
90
internal/constants/ivpn.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const (
|
||||||
|
IvpnCA = "MIIGoDCCBIigAwIBAgIJAJjvUclXmxtnMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJDSDEPMA0GA1UECAwGWnVyaWNoMQ8wDQYDVQQHDAZadXJpY2gxETAPBgNVBAoMCElWUE4ubmV0MQ0wCwYDVQQLDARJVlBOMRgwFgYDVQQDDA9JVlBOIFJvb3QgQ0EgdjIxHzAdBgkqhkiG9w0BCQEWEHN1cHBvcnRAaXZwbi5uZXQwHhcNMjAwMjI2MTA1MjI5WhcNNDAwMjIxMTA1MjI5WjCBjDELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBlp1cmljaDEPMA0GA1UEBwwGWnVyaWNoMREwDwYDVQQKDAhJVlBOLm5ldDENMAsGA1UECwwESVZQTjEYMBYGA1UEAwwPSVZQTiBSb290IENBIHYyMR8wHQYJKoZIhvcNAQkBFhBzdXBwb3J0QGl2cG4ubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxHVeaQN3nYCLnGoEg6cY44AExbQ3W6XGKYwC9vI+HJbb1o0tAv56ryvc6eS6BdG5q9M8fHaHEE/jw9rtznioiXPwIEmqMqFPA9k1oRIQTGX73m+zHGtRpt9P4tGYhkvbqnN0OGI0H+j9R6cwKi7KpWIoTVibtyI7uuwgzC2nvDzVkLi63uvnCKRXcGy3VWC06uWFbqI9+QDrHHgdJA1F0wRfg0Iac7TE75yXItBMvNLbdZpge9SmplYWFQ2rVPG+n75KepJ+KW7PYfTP4Mh3R8A7h3/WRm03o3spf2aYw71t44voZ6agvslvwqGyczDytsLUny0U2zR7/mfEAyVbL8jqcWr2Df0m3TA0WxwdWvA51/RflVk9G96LncUkoxuBT56QSMtdjbMSqRgLfz1iPsglQEaCzUSqHfQExvONhXtNgy+Pr2+wGrEuSlLMee7aUEMTFEX/vHPZanCrUVYf5Vs8vDOirZjQSHJfgZfwj3nL5VLtIq6ekDhSAdrqCTILP3V2HbgdZGWPVQxl4YmQPKo0IJpse5Kb6TF2o0i90KhORcKg7qZA40sEbYLEwqTM7VBs1FahTXsOPAoMa7xZWV1TnigF5pdVS1l51dy5S8L4ErHFEnAp242BDuTClSLVnWDdofW0EZ0OkK7V9zKyVl75dlBgxMIS0y5MsK7IWicCAwEAAaOCAQEwgf4wHQYDVR0OBBYEFHUDcMOMo35yg2A/v0uYfkDE11CXMIHBBgNVHSMEgbkwgbaAFHUDcMOMo35yg2A/v0uYfkDE11CXoYGSpIGPMIGMMQswCQYDVQQGEwJDSDEPMA0GA1UECAwGWnVyaWNoMQ8wDQYDVQQHDAZadXJpY2gxETAPBgNVBAoMCElWUE4ubmV0MQ0wCwYDVQQLDARJVlBOMRgwFgYDVQQDDA9JVlBOIFJvb3QgQ0EgdjIxHzAdBgkqhkiG9w0BCQEWEHN1cHBvcnRAaXZwbi5uZXSCCQCY71HJV5sbZzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAABAjRMJy+mXFLezAZ8iUgxOjNtSqkCv1aU78K1XkYUzbwNNrSIVGKfP9cqOEiComXY6nniws7QEV2IWilcdPKm0x57recrr9TExGGOTVGB/WdmsFfn0g/HgmxNvXypzG3qulBk4qQTymICdsl9vIPb1l9FSjKw1KgUVuCPaYq7xiXbZ/kZdZX49xeKtoDBrXKKhXVYoWus/S+k2IS8iCxvcp599y7LQJg5DOGlbaxFhsW4R+kfGOaegyhPvpaznguv02i7NLd99XqJhpv2jTUF5F3T23Z4KkL/wTo4zxz09DKOlELrE4ai++ilCt/mXWECXNOSNXzgszpe6WAs0h9R++sH+AzJyhBfIGgPUTxHHHvxBVLj3k6VCgF7mRP2Y+rTWa6d8AGI2+RaeyV9DVVH9UeSoU0Hv2JHiZL6dRERnyg8dyzKeTCke8poLIjXF+gyvI+22/xsL8jcNHi9Kji3Vpc3i0Mxzx3gu2N+PL71CwJilgqBgxj0firr3k8sFcWVSGos6RJ3IvFvThxYx0p255WrWM01fR9TktPYEfjDT9qpIJ8OrGlNOhWhYj+a45qibXDpaDdb/uBEmf2sSXNifjSeUyqu6cKfZvMqB7pS3l/AhuAOTT80E4sXLEoDxkFD4C78swZ8wyWRKwsWGIGABGAHwXEAoDiZ/jjFrEZT0="
|
||||||
|
IvpnOpenvpnStaticKeyV1 = "ac470c93ff9f5602a8aab37dee84a52814d10f20490ad23c47d5d82120c1bf859e93d0696b455d4a1b8d55d40c2685c41ca1d0aef29a3efd27274c4ef09020a3978fe45784b335da6df2d12db97bbb838416515f2a96f04715fd28949c6fe296a925cfada3f8b8928ed7fc963c1563272f5cf46e5e1d9c845d7703ca881497b7e6564a9d1dea9358adffd435295479f47d5298fabf5359613ff5992cb57ff081a04dfb81a26513a6b44a9b5490ad265f8a02384832a59cc3e075ad545461060b7bcab49bac815163cb80983dd51d5b1fd76170ffd904d8291071e96efc3fb777856c717b148d08a510f5687b8a8285dcffe737b98916dd15ef6235dee4266d3b"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IvpnCountryChoices() (choices []string) {
|
||||||
|
servers := IvpnServers()
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Country
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnCityChoices() (choices []string) {
|
||||||
|
servers := IvpnServers()
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].City
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IvpnHostnameChoices() (choices []string) {
|
||||||
|
servers := IvpnServers()
|
||||||
|
choices = make([]string, len(servers))
|
||||||
|
for i := range servers {
|
||||||
|
choices[i] = servers[i].Hostname
|
||||||
|
}
|
||||||
|
return makeUnique(choices)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
// IvpnServers returns a slice of all the server information for Ivpn.
|
||||||
|
func IvpnServers() []models.IvpnServer {
|
||||||
|
return []models.IvpnServer{
|
||||||
|
{Country: "Australia", City: "", Hostname: "au-nsw.gw.ivpn.net", IPs: []net.IP{{46, 102, 153, 242}}},
|
||||||
|
{Country: "Austria", City: "", Hostname: "at.gw.ivpn.net", IPs: []net.IP{{185, 244, 212, 66}}},
|
||||||
|
{Country: "Belgium", City: "", Hostname: "be.gw.ivpn.net", IPs: []net.IP{{194, 187, 251, 10}}},
|
||||||
|
{Country: "Brazil", City: "", Hostname: "br.gw.ivpn.net", IPs: []net.IP{{45, 162, 229, 130}}},
|
||||||
|
{Country: "Canada", City: "Montreal", Hostname: "ca-qc.gw.ivpn.net", IPs: []net.IP{{87, 101, 92, 26}}},
|
||||||
|
{Country: "Canada", City: "Toronto", Hostname: "ca.gw.ivpn.net", IPs: []net.IP{{104, 254, 90, 178}}},
|
||||||
|
{Country: "Czech Republic", City: "", Hostname: "cz.gw.ivpn.net", IPs: []net.IP{{195, 181, 160, 167}}},
|
||||||
|
{Country: "Denmark", City: "", Hostname: "dk.gw.ivpn.net", IPs: []net.IP{{185, 245, 84, 226}}},
|
||||||
|
{Country: "Finland", City: "", Hostname: "fi.gw.ivpn.net", IPs: []net.IP{{185, 112, 82, 12}}},
|
||||||
|
{Country: "France", City: "", Hostname: "fr.gw.ivpn.net", IPs: []net.IP{{185, 246, 211, 179}}},
|
||||||
|
{Country: "Germany", City: "", Hostname: "de.gw.ivpn.net", IPs: []net.IP{{178, 162, 211, 114}}},
|
||||||
|
{Country: "Hong Kong", City: "", Hostname: "hk.gw.ivpn.net", IPs: []net.IP{{209, 58, 188, 13}}},
|
||||||
|
{Country: "Hungary", City: "", Hostname: "hu.gw.ivpn.net", IPs: []net.IP{{185, 189, 114, 186}}},
|
||||||
|
{Country: "Iceland", City: "", Hostname: "is.gw.ivpn.net", IPs: []net.IP{{82, 221, 107, 178}}},
|
||||||
|
{Country: "Israel", City: "", Hostname: "il.gw.ivpn.net", IPs: []net.IP{{185, 191, 207, 194}}},
|
||||||
|
{Country: "Italy", City: "", Hostname: "it.gw.ivpn.net", IPs: []net.IP{{158, 58, 172, 73}}},
|
||||||
|
{Country: "Japan", City: "", Hostname: "jp.gw.ivpn.net", IPs: []net.IP{{91, 207, 174, 234}}},
|
||||||
|
{Country: "Luxembourg", City: "", Hostname: "lu.gw.ivpn.net", IPs: []net.IP{{92, 223, 89, 53}}},
|
||||||
|
{Country: "Netherlands", City: "", Hostname: "nl.gw.ivpn.net", IPs: []net.IP{{95, 211, 172, 95}}},
|
||||||
|
{Country: "Norway", City: "", Hostname: "no.gw.ivpn.net", IPs: []net.IP{{194, 242, 10, 150}}},
|
||||||
|
{Country: "Poland", City: "", Hostname: "pl.gw.ivpn.net", IPs: []net.IP{{185, 246, 208, 86}}},
|
||||||
|
{Country: "Portugal", City: "", Hostname: "pt.gw.ivpn.net", IPs: []net.IP{{94, 46, 175, 112}}},
|
||||||
|
{Country: "Romania", City: "", Hostname: "ro.gw.ivpn.net", IPs: []net.IP{{37, 120, 206, 50}}},
|
||||||
|
{Country: "Serbia", City: "", Hostname: "rs.gw.ivpn.net", IPs: []net.IP{{141, 98, 103, 250}}},
|
||||||
|
{Country: "Singapore", City: "", Hostname: "sg.gw.ivpn.net", IPs: []net.IP{{185, 128, 24, 186}}},
|
||||||
|
{Country: "Slovakia", City: "", Hostname: "sk.gw.ivpn.net", IPs: []net.IP{{185, 245, 85, 250}}},
|
||||||
|
{Country: "Sweden", City: "", Hostname: "se.gw.ivpn.net", IPs: []net.IP{{80, 67, 10, 138}}},
|
||||||
|
{Country: "Switzerland", City: "", Hostname: "ch.gw.ivpn.net", IPs: []net.IP{{185, 212, 170, 138}}},
|
||||||
|
{Country: "USA", City: "Atlanta", Hostname: "us-ga.gw.ivpn.net", IPs: []net.IP{{104, 129, 24, 146}}},
|
||||||
|
{Country: "USA", City: "Chicago", Hostname: "us-il.gw.ivpn.net", IPs: []net.IP{{72, 11, 137, 146}}},
|
||||||
|
{Country: "USA", City: "Dallas", Hostname: "us-tx.gw.ivpn.net", IPs: []net.IP{{96, 44, 189, 194}}},
|
||||||
|
{Country: "USA", City: "Las Vegas", Hostname: "us-nv.gw.ivpn.net", IPs: []net.IP{{185, 242, 5, 34}}},
|
||||||
|
{Country: "USA", City: "Los Angeles", Hostname: "us-ca.gw.ivpn.net", IPs: []net.IP{{69, 12, 80, 146}}},
|
||||||
|
{Country: "USA", City: "Miami", Hostname: "us-fl.gw.ivpn.net", IPs: []net.IP{{173, 44, 49, 90}}},
|
||||||
|
{Country: "USA", City: "New Jersey", Hostname: "us-nj.gw.ivpn.net", IPs: []net.IP{{23, 226, 128, 18}}},
|
||||||
|
{Country: "USA", City: "New York", Hostname: "us-ny.gw.ivpn.net", IPs: []net.IP{{64, 120, 44, 114}}},
|
||||||
|
{Country: "USA", City: "Phoenix", Hostname: "us-az.gw.ivpn.net", IPs: []net.IP{{193, 37, 254, 130}}},
|
||||||
|
{Country: "USA", City: "Salt Lake City", Hostname: "us-ut.gw.ivpn.net", IPs: []net.IP{{198, 105, 216, 28}}},
|
||||||
|
{Country: "USA", City: "Seattle", Hostname: "us-wa.gw.ivpn.net", IPs: []net.IP{{23, 19, 87, 209}}},
|
||||||
|
{Country: "USA", City: "Washington", Hostname: "us-dc.gw.ivpn.net", IPs: []net.IP{{207, 244, 108, 207}}},
|
||||||
|
{Country: "Ukraine", City: "", Hostname: "ua.gw.ivpn.net", IPs: []net.IP{{193, 203, 48, 54}}},
|
||||||
|
{Country: "United Kingdom", City: "London", Hostname: "gb.gw.ivpn.net", IPs: []net.IP{{185, 59, 221, 133}, {185, 59, 221, 88}}},
|
||||||
|
{Country: "United Kingdom", City: "Manchester", Hostname: "gb-man.gw.ivpn.net", IPs: []net.IP{{89, 238, 141, 228}}},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,11 @@ func GetAllServers() (allServers models.AllServers) {
|
|||||||
Timestamp: 1620435633,
|
Timestamp: 1620435633,
|
||||||
Servers: HideMyAssServers(),
|
Servers: HideMyAssServers(),
|
||||||
},
|
},
|
||||||
|
Ivpn: models.IvpnServers{
|
||||||
|
Version: 1,
|
||||||
|
Timestamp: 1622406883,
|
||||||
|
Servers: IvpnServers(),
|
||||||
|
},
|
||||||
Mullvad: models.MullvadServers{
|
Mullvad: models.MullvadServers{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Timestamp: 1620500848,
|
Timestamp: 1620500848,
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ func Test_versions(t *testing.T) {
|
|||||||
version: allServers.HideMyAss.Version,
|
version: allServers.HideMyAss.Version,
|
||||||
digest: "a93b4057",
|
digest: "a93b4057",
|
||||||
},
|
},
|
||||||
|
"Ivpn": {
|
||||||
|
model: models.IvpnServer{},
|
||||||
|
version: allServers.Ivpn.Version,
|
||||||
|
digest: "2eb80d28",
|
||||||
|
},
|
||||||
"Mullvad": {
|
"Mullvad": {
|
||||||
model: models.MullvadServer{},
|
model: models.MullvadServer{},
|
||||||
version: allServers.Mullvad.Version,
|
version: allServers.Mullvad.Version,
|
||||||
@@ -157,6 +162,11 @@ func Test_timestamps(t *testing.T) {
|
|||||||
timestamp: allServers.HideMyAss.Timestamp,
|
timestamp: allServers.HideMyAss.Timestamp,
|
||||||
digest: "8f872ac4",
|
digest: "8f872ac4",
|
||||||
},
|
},
|
||||||
|
"Ivpn": {
|
||||||
|
servers: allServers.Ivpn.Servers,
|
||||||
|
timestamp: allServers.Ivpn.Timestamp,
|
||||||
|
digest: "a648c5f1",
|
||||||
|
},
|
||||||
"Mullvad": {
|
"Mullvad": {
|
||||||
servers: allServers.Mullvad.Servers,
|
servers: allServers.Mullvad.Servers,
|
||||||
timestamp: allServers.Mullvad.Timestamp,
|
timestamp: allServers.Mullvad.Timestamp,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const (
|
|||||||
Fastestvpn = "fastestvpn"
|
Fastestvpn = "fastestvpn"
|
||||||
// HideMyAss is a VPN provider.
|
// HideMyAss is a VPN provider.
|
||||||
HideMyAss = "hidemyass"
|
HideMyAss = "hidemyass"
|
||||||
|
// Ivpn is a VPN provider.
|
||||||
|
Ivpn = "ivpn"
|
||||||
// Mullvad is a VPN provider.
|
// Mullvad is a VPN provider.
|
||||||
Mullvad = "mullvad"
|
Mullvad = "mullvad"
|
||||||
// NordVPN is a VPN provider.
|
// NordVPN is a VPN provider.
|
||||||
|
|||||||
@@ -47,6 +47,20 @@ func (s *HideMyAssServer) String() string {
|
|||||||
s.Country, s.Region, s.City, s.Hostname, s.TCP, s.UDP, goStringifyIPs(s.IPs))
|
s.Country, s.Region, s.City, s.Hostname, s.TCP, s.UDP, goStringifyIPs(s.IPs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IvpnServer struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
TCP bool `json:"tcp"`
|
||||||
|
UDP bool `json:"udp"`
|
||||||
|
IPs []net.IP `json:"ips"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IvpnServer) String() string {
|
||||||
|
return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, IPs: %s}",
|
||||||
|
s.Country, s.City, s.Hostname, goStringifyIPs(s.IPs))
|
||||||
|
}
|
||||||
|
|
||||||
type MullvadServer struct {
|
type MullvadServer struct {
|
||||||
IPs []net.IP `json:"ips"`
|
IPs []net.IP `json:"ips"`
|
||||||
IPsV6 []net.IP `json:"ipsv6"`
|
IPsV6 []net.IP `json:"ipsv6"`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type AllServers struct {
|
|||||||
Cyberghost CyberghostServers `json:"cyberghost"`
|
Cyberghost CyberghostServers `json:"cyberghost"`
|
||||||
Fastestvpn FastestvpnServers `json:"fastestvpn"`
|
Fastestvpn FastestvpnServers `json:"fastestvpn"`
|
||||||
HideMyAss HideMyAssServers `json:"hidemyass"`
|
HideMyAss HideMyAssServers `json:"hidemyass"`
|
||||||
|
Ivpn IvpnServers `json:"ivpn"`
|
||||||
Mullvad MullvadServers `json:"mullvad"`
|
Mullvad MullvadServers `json:"mullvad"`
|
||||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||||
Privado PrivadoServers `json:"privado"`
|
Privado PrivadoServers `json:"privado"`
|
||||||
@@ -22,6 +23,7 @@ func (a *AllServers) Count() int {
|
|||||||
return len(a.Cyberghost.Servers) +
|
return len(a.Cyberghost.Servers) +
|
||||||
len(a.Fastestvpn.Servers) +
|
len(a.Fastestvpn.Servers) +
|
||||||
len(a.HideMyAss.Servers) +
|
len(a.HideMyAss.Servers) +
|
||||||
|
len(a.Ivpn.Servers) +
|
||||||
len(a.Mullvad.Servers) +
|
len(a.Mullvad.Servers) +
|
||||||
len(a.Nordvpn.Servers) +
|
len(a.Nordvpn.Servers) +
|
||||||
len(a.Privado.Servers) +
|
len(a.Privado.Servers) +
|
||||||
@@ -50,6 +52,11 @@ type HideMyAssServers struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Servers []HideMyAssServer `json:"servers"`
|
Servers []HideMyAssServer `json:"servers"`
|
||||||
}
|
}
|
||||||
|
type IvpnServers struct {
|
||||||
|
Version uint16 `json:"version"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Servers []IvpnServer `json:"servers"`
|
||||||
|
}
|
||||||
type MullvadServers struct {
|
type MullvadServers struct {
|
||||||
Version uint16 `json:"version"`
|
Version uint16 `json:"version"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ func setConnectionToLines(lines []string, connection models.OpenVPNConnection) (
|
|||||||
lines[i] = "proto " + connection.Protocol
|
lines[i] = "proto " + connection.Protocol
|
||||||
|
|
||||||
case strings.HasPrefix(line, "remote "):
|
case strings.HasPrefix(line, "remote "):
|
||||||
lines[i] = "remote " + connection.IP.String() + " " + strconv.Itoa(int(connection.Port))
|
lines[i] = "remote " + connection.RemoteLine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
45
internal/provider/ivpn/connection.go
Normal file
45
internal/provider/ivpn/connection.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrProtocolUnsupported = errors.New("network protocol is not supported")
|
||||||
|
|
||||||
|
func (i *Ivpn) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
const port = 2049
|
||||||
|
const protocol = constants.UDP
|
||||||
|
if selection.TCP {
|
||||||
|
return connection, ErrProtocolUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := i.filterServers(selection)
|
||||||
|
if err != nil {
|
||||||
|
return connection, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections []models.OpenVPNConnection
|
||||||
|
for _, server := range servers {
|
||||||
|
for _, IP := range server.IPs {
|
||||||
|
connection := models.OpenVPNConnection{
|
||||||
|
IP: IP,
|
||||||
|
Port: port,
|
||||||
|
Protocol: protocol,
|
||||||
|
Hostname: server.Hostname,
|
||||||
|
}
|
||||||
|
connections = append(connections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.TargetIP != nil {
|
||||||
|
return utils.GetTargetIPConnection(connections, selection.TargetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PickRandomConnection(connections, i.randSource), nil
|
||||||
|
}
|
||||||
29
internal/provider/ivpn/filter.go
Normal file
29
internal/provider/ivpn/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *Ivpn) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.IvpnServer, err error) {
|
||||||
|
for _, server := range i.servers {
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||||
|
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||||
|
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||||
|
selection.TCP && !server.TCP,
|
||||||
|
!selection.TCP && !server.UDP:
|
||||||
|
default:
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return nil, utils.NoServerFoundError(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
71
internal/provider/ivpn/openvpnconf.go
Normal file
71
internal/provider/ivpn/openvpnconf.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *Ivpn) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"ping 5",
|
||||||
|
"ping-exit 30",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// IVPN specific
|
||||||
|
"remote-cert-tls server", // updated name of ns-cert-type
|
||||||
|
"comp-lzo no",
|
||||||
|
"key-direction 1",
|
||||||
|
"tls-cipher TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
|
||||||
|
|
||||||
|
// Added constant values
|
||||||
|
"mute-replay-warnings",
|
||||||
|
"auth-nocache",
|
||||||
|
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||||
|
"auth-retry nointeract",
|
||||||
|
"suppress-timestamps",
|
||||||
|
|
||||||
|
// Modified variables
|
||||||
|
"verb " + strconv.Itoa(settings.Verbosity),
|
||||||
|
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||||
|
"proto " + connection.Protocol,
|
||||||
|
"remote " + connection.RemoteLine(),
|
||||||
|
"verify-x509-name " + connection.Hostname, // + " name-prefix"
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||||
|
|
||||||
|
if settings.Auth != "" {
|
||||||
|
lines = append(lines, "auth "+settings.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.MSSFix > 0 {
|
||||||
|
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.Root {
|
||||||
|
lines = append(lines, "user "+username)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.WrapOpenvpnCA(
|
||||||
|
constants.IvpnCA)...)
|
||||||
|
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||||
|
constants.IvpnOpenvpnStaticKeyV1)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/ivpn/portforward.go
Normal file
17
internal/provider/ivpn/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *Ivpn) 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 Ivpn")
|
||||||
|
}
|
||||||
19
internal/provider/ivpn/provider.go
Normal file
19
internal/provider/ivpn/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ivpn struct {
|
||||||
|
servers []models.IvpnServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.IvpnServer, randSource rand.Source) *Ivpn {
|
||||||
|
return &Ivpn{
|
||||||
|
servers: servers,
|
||||||
|
randSource: randSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
||||||
"github.com/qdm12/gluetun/internal/provider/fastestvpn"
|
"github.com/qdm12/gluetun/internal/provider/fastestvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/hidemyass"
|
"github.com/qdm12/gluetun/internal/provider/hidemyass"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
||||||
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privado"
|
"github.com/qdm12/gluetun/internal/provider/privado"
|
||||||
@@ -48,6 +49,8 @@ func New(provider string, allServers models.AllServers, timeNow func() time.Time
|
|||||||
return fastestvpn.New(allServers.Fastestvpn.Servers, randSource)
|
return fastestvpn.New(allServers.Fastestvpn.Servers, randSource)
|
||||||
case constants.HideMyAss:
|
case constants.HideMyAss:
|
||||||
return hidemyass.New(allServers.HideMyAss.Servers, randSource)
|
return hidemyass.New(allServers.HideMyAss.Servers, randSource)
|
||||||
|
case constants.Ivpn:
|
||||||
|
return ivpn.New(allServers.Ivpn.Servers, randSource)
|
||||||
case constants.Mullvad:
|
case constants.Mullvad:
|
||||||
return mullvad.New(allServers.Mullvad.Servers, randSource)
|
return mullvad.New(allServers.Mullvad.Servers, randSource)
|
||||||
case constants.Nordvpn:
|
case constants.Nordvpn:
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func (s *storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
|
|||||||
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
|
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
|
||||||
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
|
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
|
||||||
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
|
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
|
||||||
|
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
|
||||||
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
|
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
|
||||||
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
|
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
|
||||||
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
|
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
|
||||||
@@ -90,6 +91,19 @@ func (s *storage) mergeHideMyAss(hardcoded, persisted models.HideMyAssServers) m
|
|||||||
return persisted
|
return persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storage) mergeIvpn(hardcoded, persisted models.IvpnServers) models.IvpnServers {
|
||||||
|
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||||
|
return hardcoded
|
||||||
|
}
|
||||||
|
versionDiff := hardcoded.Version - persisted.Version
|
||||||
|
if versionDiff > 0 {
|
||||||
|
s.logVersionDiff("Ivpn", versionDiff)
|
||||||
|
return hardcoded
|
||||||
|
}
|
||||||
|
s.logTimeDiff("Ivpn", persisted.Timestamp, hardcoded.Timestamp)
|
||||||
|
return persisted
|
||||||
|
}
|
||||||
|
|
||||||
func (s *storage) mergeMullvad(hardcoded, persisted models.MullvadServers) models.MullvadServers {
|
func (s *storage) mergeMullvad(hardcoded, persisted models.MullvadServers) models.MullvadServers {
|
||||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||||
return hardcoded
|
return hardcoded
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func countServers(allServers models.AllServers) int {
|
|||||||
return len(allServers.Cyberghost.Servers) +
|
return len(allServers.Cyberghost.Servers) +
|
||||||
len(allServers.Fastestvpn.Servers) +
|
len(allServers.Fastestvpn.Servers) +
|
||||||
len(allServers.HideMyAss.Servers) +
|
len(allServers.HideMyAss.Servers) +
|
||||||
|
len(allServers.Ivpn.Servers) +
|
||||||
len(allServers.Mullvad.Servers) +
|
len(allServers.Mullvad.Servers) +
|
||||||
len(allServers.Nordvpn.Servers) +
|
len(allServers.Nordvpn.Servers) +
|
||||||
len(allServers.Privado.Servers) +
|
len(allServers.Privado.Servers) +
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/updater/providers/cyberghost"
|
"github.com/qdm12/gluetun/internal/updater/providers/cyberghost"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/fastestvpn"
|
"github.com/qdm12/gluetun/internal/updater/providers/fastestvpn"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/hidemyass"
|
"github.com/qdm12/gluetun/internal/updater/providers/hidemyass"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/providers/ivpn"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/mullvad"
|
"github.com/qdm12/gluetun/internal/updater/providers/mullvad"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/nordvpn"
|
"github.com/qdm12/gluetun/internal/updater/providers/nordvpn"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/pia"
|
"github.com/qdm12/gluetun/internal/updater/providers/pia"
|
||||||
@@ -74,6 +75,26 @@ func (u *updater) updateHideMyAss(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updater) updateIvpn(ctx context.Context) (err error) {
|
||||||
|
minServers := getMinServers(len(u.servers.Ivpn.Servers))
|
||||||
|
servers, warnings, err := ivpn.GetServers(
|
||||||
|
ctx, u.unzipper, u.presolver, minServers)
|
||||||
|
if u.options.CLI {
|
||||||
|
for _, warning := range warnings {
|
||||||
|
u.logger.Warn("Ivpn: %s", warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if u.options.Stdout {
|
||||||
|
u.println(ivpn.Stringify(servers))
|
||||||
|
}
|
||||||
|
u.servers.Ivpn.Timestamp = u.timeNow().Unix()
|
||||||
|
u.servers.Ivpn.Servers = servers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *updater) updateMullvad(ctx context.Context) (err error) {
|
func (u *updater) updateMullvad(ctx context.Context) (err error) {
|
||||||
minServers := getMinServers(len(u.servers.Mullvad.Servers))
|
minServers := getMinServers(len(u.servers.Mullvad.Servers))
|
||||||
servers, err := mullvad.GetServers(ctx, u.client, minServers)
|
servers, err := mullvad.GetServers(ctx, u.client, minServers)
|
||||||
|
|||||||
16
internal/updater/providers/ivpn/filename.go
Normal file
16
internal/updater/providers/ivpn/filename.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseFilename(fileName string) (country, city string) {
|
||||||
|
const suffix = ".ovpn"
|
||||||
|
fileName = strings.TrimSuffix(fileName, suffix)
|
||||||
|
parts := strings.Split(fileName, "-")
|
||||||
|
country = strings.ReplaceAll(parts[0], "_", " ")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
city = strings.ReplaceAll(parts[1], "_", " ")
|
||||||
|
}
|
||||||
|
return country, city
|
||||||
|
}
|
||||||
41
internal/updater/providers/ivpn/filename_test.go
Normal file
41
internal/updater/providers/ivpn/filename_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseFilename(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fileName string
|
||||||
|
country string
|
||||||
|
city string
|
||||||
|
}{
|
||||||
|
"empty filename": {},
|
||||||
|
"country only": {
|
||||||
|
fileName: "Country.ovpn",
|
||||||
|
country: "Country",
|
||||||
|
},
|
||||||
|
"country and city": {
|
||||||
|
fileName: "Country-City.ovpn",
|
||||||
|
country: "Country",
|
||||||
|
city: "City",
|
||||||
|
},
|
||||||
|
"composite country and city": {
|
||||||
|
fileName: "Coun_try-Ci_ty.ovpn",
|
||||||
|
country: "Coun try",
|
||||||
|
city: "Ci ty",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
country, city := parseFilename(testCase.fileName)
|
||||||
|
assert.Equal(t, testCase.country, country)
|
||||||
|
assert.Equal(t, testCase.city, city)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
58
internal/updater/providers/ivpn/hosttoserver.go
Normal file
58
internal/updater/providers/ivpn/hosttoserver.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hostToServer map[string]models.IvpnServer
|
||||||
|
|
||||||
|
func (hts hostToServer) add(host, country, city string, tcp, udp bool) {
|
||||||
|
server, ok := hts[host]
|
||||||
|
if !ok {
|
||||||
|
server.Hostname = host
|
||||||
|
server.Country = country
|
||||||
|
server.City = city
|
||||||
|
}
|
||||||
|
if tcp {
|
||||||
|
server.TCP = tcp
|
||||||
|
}
|
||||||
|
if udp {
|
||||||
|
server.UDP = udp
|
||||||
|
}
|
||||||
|
hts[host] = server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hts hostToServer) toHostsSlice() (hosts []string) {
|
||||||
|
hosts = make([]string, 0, len(hts))
|
||||||
|
for host := range hts {
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
sort.Slice(hosts, func(i, j int) bool {
|
||||||
|
return hosts[i] < hosts[j]
|
||||||
|
})
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) {
|
||||||
|
for host, IPs := range hostToIPs {
|
||||||
|
server := hts[host]
|
||||||
|
server.IPs = IPs
|
||||||
|
hts[host] = server
|
||||||
|
}
|
||||||
|
for host, server := range hts {
|
||||||
|
if len(server.IPs) == 0 {
|
||||||
|
delete(hts, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hts hostToServer) toServersSlice() (servers []models.IvpnServer) {
|
||||||
|
servers = make([]models.IvpnServer, 0, len(hts))
|
||||||
|
for _, server := range hts {
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
return servers
|
||||||
|
}
|
||||||
211
internal/updater/providers/ivpn/hosttoserver_test.go
Normal file
211
internal/updater/providers/ivpn/hosttoserver_test.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_hostToServer_add(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
initialHTS hostToServer
|
||||||
|
host string
|
||||||
|
country string
|
||||||
|
city string
|
||||||
|
tcp bool
|
||||||
|
udp bool
|
||||||
|
expectedHTS hostToServer
|
||||||
|
}{
|
||||||
|
"empty host to server": {
|
||||||
|
initialHTS: hostToServer{},
|
||||||
|
host: "host",
|
||||||
|
country: "country",
|
||||||
|
city: "city",
|
||||||
|
tcp: true,
|
||||||
|
udp: true,
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"host": {
|
||||||
|
Hostname: "host",
|
||||||
|
Country: "country",
|
||||||
|
City: "city",
|
||||||
|
TCP: true,
|
||||||
|
UDP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"add server": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"existing host": {},
|
||||||
|
},
|
||||||
|
host: "host",
|
||||||
|
country: "country",
|
||||||
|
city: "city",
|
||||||
|
tcp: true,
|
||||||
|
udp: true,
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"existing host": {},
|
||||||
|
"host": models.IvpnServer{
|
||||||
|
Hostname: "host",
|
||||||
|
Country: "country",
|
||||||
|
City: "city",
|
||||||
|
TCP: true,
|
||||||
|
UDP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"extend existing server": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"host": models.IvpnServer{
|
||||||
|
Hostname: "host",
|
||||||
|
Country: "country",
|
||||||
|
City: "city",
|
||||||
|
TCP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host: "host",
|
||||||
|
country: "country",
|
||||||
|
city: "city",
|
||||||
|
tcp: false,
|
||||||
|
udp: true,
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"host": models.IvpnServer{
|
||||||
|
Hostname: "host",
|
||||||
|
Country: "country",
|
||||||
|
City: "city",
|
||||||
|
TCP: true,
|
||||||
|
UDP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCase.initialHTS.add(testCase.host, testCase.country, testCase.city, testCase.tcp, testCase.udp)
|
||||||
|
assert.Equal(t, testCase.expectedHTS, testCase.initialHTS)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_hostToServer_toHostsSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
hts hostToServer
|
||||||
|
hosts []string
|
||||||
|
}{
|
||||||
|
"empty host to server": {
|
||||||
|
hts: hostToServer{},
|
||||||
|
hosts: []string{},
|
||||||
|
},
|
||||||
|
"single host": {
|
||||||
|
hts: hostToServer{
|
||||||
|
"A": {},
|
||||||
|
},
|
||||||
|
hosts: []string{"A"},
|
||||||
|
},
|
||||||
|
"multiple hosts": {
|
||||||
|
hts: hostToServer{
|
||||||
|
"A": {},
|
||||||
|
"B": {},
|
||||||
|
},
|
||||||
|
hosts: []string{"A", "B"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
hosts := testCase.hts.toHostsSlice()
|
||||||
|
assert.ElementsMatch(t, testCase.hosts, hosts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_hostToServer_adaptWithIPs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
initialHTS hostToServer
|
||||||
|
hostToIPs map[string][]net.IP
|
||||||
|
expectedHTS hostToServer
|
||||||
|
}{
|
||||||
|
"create server": {
|
||||||
|
initialHTS: hostToServer{},
|
||||||
|
hostToIPs: map[string][]net.IP{
|
||||||
|
"A": {{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"A": models.IvpnServer{
|
||||||
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"add IPs to existing server": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"A": models.IvpnServer{
|
||||||
|
Country: "country",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hostToIPs: map[string][]net.IP{
|
||||||
|
"A": {{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"A": models.IvpnServer{
|
||||||
|
Country: "country",
|
||||||
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"remove server without IP": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"A": models.IvpnServer{
|
||||||
|
Country: "country",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hostToIPs: map[string][]net.IP{},
|
||||||
|
expectedHTS: hostToServer{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCase.initialHTS.adaptWithIPs(testCase.hostToIPs)
|
||||||
|
assert.Equal(t, testCase.expectedHTS, testCase.initialHTS)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_hostToServer_toServersSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
hts hostToServer
|
||||||
|
servers []models.IvpnServer
|
||||||
|
}{
|
||||||
|
"empty host to server": {
|
||||||
|
hts: hostToServer{},
|
||||||
|
servers: []models.IvpnServer{},
|
||||||
|
},
|
||||||
|
"multiple servers": {
|
||||||
|
hts: hostToServer{
|
||||||
|
"A": {Country: "A"},
|
||||||
|
"B": {Country: "B"},
|
||||||
|
},
|
||||||
|
servers: []models.IvpnServer{
|
||||||
|
{Country: "A"},
|
||||||
|
{Country: "B"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
servers := testCase.hts.toServersSlice()
|
||||||
|
assert.ElementsMatch(t, testCase.servers, servers)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
36
internal/updater/providers/ivpn/resolve.go
Normal file
36
internal/updater/providers/ivpn/resolve.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getResolveSettings(minServers int) (settings resolver.ParallelSettings) {
|
||||||
|
const (
|
||||||
|
maxFailRatio = 0.1
|
||||||
|
maxDuration = 20 * time.Second
|
||||||
|
betweenDuration = time.Second
|
||||||
|
maxNoNew = 2
|
||||||
|
maxFails = 2
|
||||||
|
)
|
||||||
|
return resolver.ParallelSettings{
|
||||||
|
MaxFailRatio: maxFailRatio,
|
||||||
|
MinFound: minServers,
|
||||||
|
Repeat: resolver.RepeatSettings{
|
||||||
|
MaxDuration: maxDuration,
|
||||||
|
BetweenDuration: betweenDuration,
|
||||||
|
MaxNoNew: maxNoNew,
|
||||||
|
MaxFails: maxFails,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveHosts(ctx context.Context, presolver resolver.Parallel,
|
||||||
|
hosts []string, minServers int) (hostToIPs map[string][]net.IP,
|
||||||
|
warnings []string, err error) {
|
||||||
|
settings := getResolveSettings(minServers)
|
||||||
|
return presolver.Resolve(ctx, hosts, settings)
|
||||||
|
}
|
||||||
56
internal/updater/providers/ivpn/resolve_test.go
Normal file
56
internal/updater/providers/ivpn/resolve_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver/mock_resolver"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_resolveHosts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
presolver := mock_resolver.NewMockParallel(ctrl)
|
||||||
|
hosts := []string{"host1", "host2"}
|
||||||
|
const minServers = 10
|
||||||
|
|
||||||
|
expectedHostToIPs := map[string][]net.IP{
|
||||||
|
"host1": {{1, 2, 3, 4}},
|
||||||
|
"host2": {{2, 3, 4, 5}},
|
||||||
|
}
|
||||||
|
expectedWarnings := []string{"warning1", "warning2"}
|
||||||
|
expectedErr := errors.New("dummy")
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxFailRatio = 0.1
|
||||||
|
maxDuration = 20 * time.Second
|
||||||
|
betweenDuration = time.Second
|
||||||
|
maxNoNew = 2
|
||||||
|
maxFails = 2
|
||||||
|
)
|
||||||
|
expectedSettings := resolver.ParallelSettings{
|
||||||
|
MaxFailRatio: maxFailRatio,
|
||||||
|
MinFound: minServers,
|
||||||
|
Repeat: resolver.RepeatSettings{
|
||||||
|
MaxDuration: maxDuration,
|
||||||
|
BetweenDuration: betweenDuration,
|
||||||
|
MaxNoNew: maxNoNew,
|
||||||
|
MaxFails: maxFails,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
presolver.EXPECT().Resolve(ctx, hosts, expectedSettings).
|
||||||
|
Return(expectedHostToIPs, expectedWarnings, expectedErr)
|
||||||
|
|
||||||
|
hostToIPs, warnings, err := resolveHosts(ctx, presolver, hosts, minServers)
|
||||||
|
assert.Equal(t, expectedHostToIPs, hostToIPs)
|
||||||
|
assert.Equal(t, expectedWarnings, warnings)
|
||||||
|
assert.Equal(t, expectedErr, err)
|
||||||
|
}
|
||||||
86
internal/updater/providers/ivpn/servers.go
Normal file
86
internal/updater/providers/ivpn/servers.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Package ivpn contains code to obtain the server information
|
||||||
|
// for the Surshark provider.
|
||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/openvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotEnoughServers = errors.New("not enough servers found")
|
||||||
|
|
||||||
|
func GetServers(ctx context.Context, unzipper unzip.Unzipper,
|
||||||
|
presolver resolver.Parallel, minServers int) (
|
||||||
|
servers []models.IvpnServer, warnings []string, err error) {
|
||||||
|
const url = "https://www.ivpn.net/releases/config/ivpn-openvpn-config.zip"
|
||||||
|
contents, err := unzipper.FetchAndExtract(ctx, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else if len(contents) < minServers {
|
||||||
|
return nil, nil, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
|
ErrNotEnoughServers, len(contents), minServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
hts := make(hostToServer)
|
||||||
|
|
||||||
|
for fileName, content := range contents {
|
||||||
|
if !strings.HasSuffix(fileName, ".ovpn") {
|
||||||
|
continue // not an OpenVPN file
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp, udp, err := openvpn.ExtractProto(content)
|
||||||
|
if err != nil {
|
||||||
|
// treat error as warning and go to next file
|
||||||
|
warning := err.Error() + ": in " + fileName
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
host, warning, err := openvpn.ExtractHost(content)
|
||||||
|
if warning != "" {
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// treat error as warning and go to next file
|
||||||
|
warning := err.Error() + " in " + fileName
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
country, city := parseFilename(fileName)
|
||||||
|
|
||||||
|
hts.add(host, country, city, tcp, udp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hts) < minServers {
|
||||||
|
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
|
ErrNotEnoughServers, len(hts), minServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts := hts.toHostsSlice()
|
||||||
|
hostToIPs, newWarnings, err := resolveHosts(ctx, presolver, hosts, minServers)
|
||||||
|
warnings = append(warnings, newWarnings...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hts.adaptWithIPs(hostToIPs)
|
||||||
|
|
||||||
|
servers = hts.toServersSlice()
|
||||||
|
|
||||||
|
if len(servers) < minServers {
|
||||||
|
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
|
ErrNotEnoughServers, len(servers), minServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
sortServers(servers)
|
||||||
|
|
||||||
|
return servers, warnings, nil
|
||||||
|
}
|
||||||
142
internal/updater/providers/ivpn/servers_test.go
Normal file
142
internal/updater/providers/ivpn/servers_test.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver/mock_resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/unzip/mock_unzip"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_GetServers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
// Inputs
|
||||||
|
minServers int
|
||||||
|
|
||||||
|
// Unzip
|
||||||
|
unzipContents map[string][]byte
|
||||||
|
unzipErr error
|
||||||
|
|
||||||
|
// Resolution
|
||||||
|
expectResolve bool
|
||||||
|
hostsToResolve []string
|
||||||
|
resolveSettings resolver.ParallelSettings
|
||||||
|
hostToIPs map[string][]net.IP
|
||||||
|
resolveWarnings []string
|
||||||
|
resolveErr error
|
||||||
|
|
||||||
|
// Output
|
||||||
|
servers []models.IvpnServer
|
||||||
|
warnings []string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"unzipper error": {
|
||||||
|
unzipErr: errors.New("dummy"),
|
||||||
|
err: errors.New("dummy"),
|
||||||
|
},
|
||||||
|
"not enough unzip contents": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{},
|
||||||
|
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||||
|
},
|
||||||
|
"no openvpn file": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{"somefile.txt": {}},
|
||||||
|
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||||
|
},
|
||||||
|
"invalid proto": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{"badproto.ovpn": []byte(`proto invalid`)},
|
||||||
|
warnings: []string{"unknown protocol: invalid: in badproto.ovpn"},
|
||||||
|
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||||
|
},
|
||||||
|
"no host": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{"nohost.ovpn": []byte(``)},
|
||||||
|
warnings: []string{"remote host not found in nohost.ovpn"},
|
||||||
|
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||||
|
},
|
||||||
|
"multiple hosts": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{
|
||||||
|
"MultiHosts.ovpn": []byte("remote hosta\nremote hostb"),
|
||||||
|
},
|
||||||
|
expectResolve: true,
|
||||||
|
hostsToResolve: []string{"hosta"},
|
||||||
|
resolveSettings: getResolveSettings(1),
|
||||||
|
warnings: []string{"only using the first host \"hosta\" and discarding 1 other hosts"},
|
||||||
|
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||||
|
},
|
||||||
|
"resolve error": {
|
||||||
|
unzipContents: map[string][]byte{
|
||||||
|
"config.ovpn": []byte("remote hosta"),
|
||||||
|
},
|
||||||
|
expectResolve: true,
|
||||||
|
hostsToResolve: []string{"hosta"},
|
||||||
|
resolveSettings: getResolveSettings(0),
|
||||||
|
resolveWarnings: []string{"resolve warning"},
|
||||||
|
resolveErr: errors.New("dummy"),
|
||||||
|
warnings: []string{"resolve warning"},
|
||||||
|
err: errors.New("dummy"),
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{
|
||||||
|
"Country1-City_A.ovpn": []byte("remote hosta"),
|
||||||
|
"Country2-City_B.ovpn": []byte("remote hostb"),
|
||||||
|
},
|
||||||
|
expectResolve: true,
|
||||||
|
hostsToResolve: []string{"hosta", "hostb"},
|
||||||
|
resolveSettings: getResolveSettings(1),
|
||||||
|
hostToIPs: map[string][]net.IP{
|
||||||
|
"hosta": {{1, 1, 1, 1}, {2, 2, 2, 2}},
|
||||||
|
"hostb": {{3, 3, 3, 3}, {4, 4, 4, 4}},
|
||||||
|
},
|
||||||
|
resolveWarnings: []string{"resolve warning"},
|
||||||
|
servers: []models.IvpnServer{
|
||||||
|
{Country: "Country1", City: "City A", Hostname: "hosta", UDP: true, IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}},
|
||||||
|
{Country: "Country2", City: "City B", Hostname: "hostb", UDP: true, IPs: []net.IP{{3, 3, 3, 3}, {4, 4, 4, 4}}},
|
||||||
|
},
|
||||||
|
warnings: []string{"resolve warning"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
unzipper := mock_unzip.NewMockUnzipper(ctrl)
|
||||||
|
const zipURL = "https://www.ivpn.net/releases/config/ivpn-openvpn-config.zip"
|
||||||
|
unzipper.EXPECT().FetchAndExtract(ctx, zipURL).
|
||||||
|
Return(testCase.unzipContents, testCase.unzipErr)
|
||||||
|
|
||||||
|
presolver := mock_resolver.NewMockParallel(ctrl)
|
||||||
|
if testCase.expectResolve {
|
||||||
|
presolver.EXPECT().Resolve(ctx, testCase.hostsToResolve, testCase.resolveSettings).
|
||||||
|
Return(testCase.hostToIPs, testCase.resolveWarnings, testCase.resolveErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, warnings, err := GetServers(ctx, unzipper, presolver, testCase.minServers)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.servers, servers)
|
||||||
|
assert.Equal(t, testCase.warnings, warnings)
|
||||||
|
if testCase.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
19
internal/updater/providers/ivpn/sort.go
Normal file
19
internal/updater/providers/ivpn/sort.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sortServers(servers []models.IvpnServer) {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
40
internal/updater/providers/ivpn/sort_test.go
Normal file
40
internal/updater/providers/ivpn/sort_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_sortServers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
initialServers []models.IvpnServer
|
||||||
|
sortedServers []models.IvpnServer
|
||||||
|
}{
|
||||||
|
"no server": {},
|
||||||
|
"sorted servers": {
|
||||||
|
initialServers: []models.IvpnServer{
|
||||||
|
{Country: "B", City: "A", Hostname: "A"},
|
||||||
|
{Country: "A", City: "A", Hostname: "B"},
|
||||||
|
{Country: "A", City: "A", Hostname: "A"},
|
||||||
|
{Country: "A", City: "B", Hostname: "A"},
|
||||||
|
},
|
||||||
|
sortedServers: []models.IvpnServer{
|
||||||
|
{Country: "A", City: "A", Hostname: "A"},
|
||||||
|
{Country: "A", City: "A", Hostname: "B"},
|
||||||
|
{Country: "A", City: "B", Hostname: "A"},
|
||||||
|
{Country: "B", City: "A", Hostname: "A"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
sortServers(testCase.initialServers)
|
||||||
|
assert.Equal(t, testCase.sortedServers, testCase.initialServers)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
14
internal/updater/providers/ivpn/string.go
Normal file
14
internal/updater/providers/ivpn/string.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func Stringify(servers []models.IvpnServer) (s string) {
|
||||||
|
s = "func IvpnServers() []models.IvpnServer {\n"
|
||||||
|
s += " return []models.IvpnServer{\n"
|
||||||
|
for _, server := range servers {
|
||||||
|
s += " " + server.String() + ",\n"
|
||||||
|
}
|
||||||
|
s += " }\n"
|
||||||
|
s += "}"
|
||||||
|
return s
|
||||||
|
}
|
||||||
43
internal/updater/providers/ivpn/string_test.go
Normal file
43
internal/updater/providers/ivpn/string_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package ivpn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Stringify(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
servers []models.IvpnServer
|
||||||
|
s string
|
||||||
|
}{
|
||||||
|
"no server": {
|
||||||
|
s: `func IvpnServers() []models.IvpnServer {
|
||||||
|
return []models.IvpnServer{
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
"multiple servers": {
|
||||||
|
servers: []models.IvpnServer{
|
||||||
|
{Country: "A"},
|
||||||
|
{Country: "B"},
|
||||||
|
},
|
||||||
|
s: `func IvpnServers() []models.IvpnServer {
|
||||||
|
return []models.IvpnServer{
|
||||||
|
{Country: "A", City: "", Hostname: "", IPs: []net.IP{}},
|
||||||
|
{Country: "B", City: "", Hostname: "", IPs: []net.IP{}},
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
s := Stringify(testCase.servers)
|
||||||
|
assert.Equal(t, testCase.s, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
53
internal/updater/resolver/mock_resolver/parallel.go
Normal file
53
internal/updater/resolver/mock_resolver/parallel.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/qdm12/gluetun/internal/updater/resolver (interfaces: Parallel)
|
||||||
|
|
||||||
|
// Package mock_resolver is a generated GoMock package.
|
||||||
|
package mock_resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
net "net"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
resolver "github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockParallel is a mock of Parallel interface.
|
||||||
|
type MockParallel struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockParallelMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockParallelMockRecorder is the mock recorder for MockParallel.
|
||||||
|
type MockParallelMockRecorder struct {
|
||||||
|
mock *MockParallel
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockParallel creates a new mock instance.
|
||||||
|
func NewMockParallel(ctrl *gomock.Controller) *MockParallel {
|
||||||
|
mock := &MockParallel{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockParallelMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockParallel) EXPECT() *MockParallelMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve mocks base method.
|
||||||
|
func (m *MockParallel) Resolve(arg0 context.Context, arg1 []string, arg2 resolver.ParallelSettings) (map[string][]net.IP, []string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Resolve", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].(map[string][]net.IP)
|
||||||
|
ret1, _ := ret[1].([]string)
|
||||||
|
ret2, _ := ret[2].(error)
|
||||||
|
return ret0, ret1, ret2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve indicates an expected call of Resolve.
|
||||||
|
func (mr *MockParallelMockRecorder) Resolve(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockParallel)(nil).Resolve), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate mockgen -destination=mock_$GOPACKAGE/$GOFILE . Parallel
|
||||||
|
|
||||||
type Parallel interface {
|
type Parallel interface {
|
||||||
Resolve(ctx context.Context, hosts []string, settings ParallelSettings) (
|
Resolve(ctx context.Context, hosts []string, settings ParallelSettings) (
|
||||||
hostToIPs map[string][]net.IP, warnings []string, err error)
|
hostToIPs map[string][]net.IP, warnings []string, err error)
|
||||||
|
|||||||
50
internal/updater/unzip/mock_unzip/unzip.go
Normal file
50
internal/updater/unzip/mock_unzip/unzip.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/qdm12/gluetun/internal/updater/unzip (interfaces: Unzipper)
|
||||||
|
|
||||||
|
// Package mock_unzip is a generated GoMock package.
|
||||||
|
package mock_unzip
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockUnzipper is a mock of Unzipper interface.
|
||||||
|
type MockUnzipper struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUnzipperMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUnzipperMockRecorder is the mock recorder for MockUnzipper.
|
||||||
|
type MockUnzipperMockRecorder struct {
|
||||||
|
mock *MockUnzipper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUnzipper creates a new mock instance.
|
||||||
|
func NewMockUnzipper(ctrl *gomock.Controller) *MockUnzipper {
|
||||||
|
mock := &MockUnzipper{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUnzipperMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockUnzipper) EXPECT() *MockUnzipperMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAndExtract mocks base method.
|
||||||
|
func (m *MockUnzipper) FetchAndExtract(arg0 context.Context, arg1 string) (map[string][]byte, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FetchAndExtract", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(map[string][]byte)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAndExtract indicates an expected call of FetchAndExtract.
|
||||||
|
func (mr *MockUnzipperMockRecorder) FetchAndExtract(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAndExtract", reflect.TypeOf((*MockUnzipper)(nil).FetchAndExtract), arg0, arg1)
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate mockgen -destination=mock_$GOPACKAGE/$GOFILE . Unzipper
|
||||||
|
|
||||||
type Unzipper interface {
|
type Unzipper interface {
|
||||||
FetchAndExtract(ctx context.Context, url string) (contents map[string][]byte, err error)
|
FetchAndExtract(ctx context.Context, url string) (contents map[string][]byte, err error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.options.Ivpn {
|
||||||
|
u.logger.Info("updating Ivpn servers...")
|
||||||
|
if err := u.updateIvpn(ctx); err != nil {
|
||||||
|
u.logger.Error(err)
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return allServers, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if u.options.Mullvad {
|
if u.options.Mullvad {
|
||||||
u.logger.Info("updating Mullvad servers...")
|
u.logger.Info("updating Mullvad servers...")
|
||||||
if err := u.updateMullvad(ctx); err != nil {
|
if err := u.updateMullvad(ctx); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user