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: IPVanish"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: IVPN"
|
- name: ":cloud: IVPN"
|
||||||
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, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
||||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN and Windscribe VPN servers
|
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, 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 54MB
|
- Based on Alpine 3.13 for a small Docker image of 54MB
|
||||||
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
|
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **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.Ipvanish, "ipvanish", false, "Update IpVanish servers")
|
||||||
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN 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")
|
||||||
|
|||||||
52
internal/configuration/ipvanish.go
Normal file
52
internal/configuration/ipvanish.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (settings *Provider) ipvanishLines() (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) readIpvanish(r reader) (err error) {
|
||||||
|
settings.Name = constants.Ipvanish
|
||||||
|
|
||||||
|
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.IpvanishCountryChoices())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IpvanishCityChoices())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.IpvanishHostnameChoices())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
192
internal/configuration/ipvanish_test.go
Normal file
192
internal/configuration/ipvanish_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_ipvanishLines(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.ipvanishLines()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.lines, lines)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Provider_readIpvanish(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.Ipvanish,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"target IP error": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ipvanish,
|
||||||
|
},
|
||||||
|
err: errDummy,
|
||||||
|
},
|
||||||
|
"countries error": {
|
||||||
|
protocol: singleStringCall{call: true},
|
||||||
|
targetIP: singleStringCall{call: true},
|
||||||
|
countries: sliceStringCall{call: true, err: errDummy},
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ipvanish,
|
||||||
|
},
|
||||||
|
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.Ipvanish,
|
||||||
|
},
|
||||||
|
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.Ipvanish,
|
||||||
|
},
|
||||||
|
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.Ipvanish,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"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.Ipvanish,
|
||||||
|
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.IpvanishCountryChoices()).
|
||||||
|
Return(testCase.countries.values, testCase.countries.err)
|
||||||
|
}
|
||||||
|
if testCase.cities.call {
|
||||||
|
env.EXPECT().CSVInside("CITY", constants.IpvanishCityChoices()).
|
||||||
|
Return(testCase.cities.values, testCase.cities.err)
|
||||||
|
}
|
||||||
|
if testCase.hostnames.call {
|
||||||
|
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IpvanishHostnameChoices()).
|
||||||
|
Return(testCase.hostnames.values, testCase.hostnames.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reader{env: env}
|
||||||
|
|
||||||
|
var settings Provider
|
||||||
|
err := settings.readIpvanish(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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,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", "ivpn", "mullvad", "nordvpn",
|
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
|
||||||
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"},
|
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"},
|
||||||
params.Default("private internet access"))
|
params.Default("private internet access"))
|
||||||
@@ -154,6 +154,8 @@ func (settings *OpenVPN) readProvider(r reader) 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.Ipvanish:
|
||||||
|
readProvider = settings.Provider.readIpvanish
|
||||||
case constants.Ivpn:
|
case constants.Ivpn:
|
||||||
readProvider = settings.Provider.readIvpn
|
readProvider = settings.Provider.readIvpn
|
||||||
case constants.Mullvad:
|
case constants.Mullvad:
|
||||||
|
|||||||
@@ -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 "ipvanish":
|
||||||
|
providerLines = settings.ipvanishLines()
|
||||||
case "ivpn":
|
case "ivpn":
|
||||||
providerLines = settings.ivpnLines()
|
providerLines = settings.ivpnLines()
|
||||||
case "mullvad":
|
case "mullvad":
|
||||||
|
|||||||
@@ -73,6 +73,23 @@ func Test_Provider_lines(t *testing.T) {
|
|||||||
" |--Hostnames: e, f",
|
" |--Hostnames: e, f",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"ipvanish": {
|
||||||
|
settings: Provider{
|
||||||
|
Name: constants.Ipvanish,
|
||||||
|
ServerSelection: ServerSelection{
|
||||||
|
Countries: []string{"a", "b"},
|
||||||
|
Cities: []string{"c", "d"},
|
||||||
|
Hostnames: []string{"e", "f"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"|--Ipvanish settings:",
|
||||||
|
" |--Network protocol: udp",
|
||||||
|
" |--Countries: a, b",
|
||||||
|
" |--Cities: c, d",
|
||||||
|
" |--Hostnames: e, f",
|
||||||
|
},
|
||||||
|
},
|
||||||
"ivpn": {
|
"ivpn": {
|
||||||
settings: Provider{
|
settings: Provider{
|
||||||
Name: constants.Ivpn,
|
Name: constants.Ivpn,
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
// Cyberghost
|
// Cyberghost
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
|
|
||||||
// Fastestvpn, HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
// Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
||||||
Countries []string `json:"countries"`
|
Countries []string `json:"countries"`
|
||||||
// HideMyAss, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, Windscribe
|
// HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, Windscribe
|
||||||
Cities []string `json:"cities"`
|
Cities []string `json:"cities"`
|
||||||
// Fastestvpn, HideMyAss, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited
|
// Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited
|
||||||
Hostnames []string `json:"hostnames"`
|
Hostnames []string `json:"hostnames"`
|
||||||
Names []string `json:"names"` // Protonvpn
|
Names []string `json:"names"` // Protonvpn
|
||||||
|
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
Ipvanish bool `json:"ipvanish"`
|
||||||
Ivpn bool `json:"ivpn"`
|
Ivpn bool `json:"ivpn"`
|
||||||
Mullvad bool `json:"mullvad"`
|
Mullvad bool `json:"mullvad"`
|
||||||
Nordvpn bool `json:"nordvpn"`
|
Nordvpn bool `json:"nordvpn"`
|
||||||
@@ -50,6 +51,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.Ipvanish = true
|
||||||
settings.Ivpn = true
|
settings.Ivpn = true
|
||||||
settings.Mullvad = true
|
settings.Mullvad = true
|
||||||
settings.Nordvpn = true
|
settings.Nordvpn = true
|
||||||
|
|||||||
1894
internal/constants/ipvanish.go
Normal file
1894
internal/constants/ipvanish.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,11 @@ func GetAllServers() (allServers models.AllServers) {
|
|||||||
Timestamp: 1620435633,
|
Timestamp: 1620435633,
|
||||||
Servers: HideMyAssServers(),
|
Servers: HideMyAssServers(),
|
||||||
},
|
},
|
||||||
|
Ipvanish: models.IpvanishServers{
|
||||||
|
Version: 1,
|
||||||
|
Timestamp: 1622430497,
|
||||||
|
Servers: IpvanishServers(),
|
||||||
|
},
|
||||||
Ivpn: models.IvpnServers{
|
Ivpn: models.IvpnServers{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Timestamp: 1624120443,
|
Timestamp: 1624120443,
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ func Test_versions(t *testing.T) {
|
|||||||
version: allServers.HideMyAss.Version,
|
version: allServers.HideMyAss.Version,
|
||||||
digest: "a93b4057",
|
digest: "a93b4057",
|
||||||
},
|
},
|
||||||
|
"Ipvanish": {
|
||||||
|
model: models.IpvanishServer{},
|
||||||
|
version: allServers.Ipvanish.Version,
|
||||||
|
digest: "2eb80d28",
|
||||||
|
},
|
||||||
"Ivpn": {
|
"Ivpn": {
|
||||||
model: models.IvpnServer{},
|
model: models.IvpnServer{},
|
||||||
version: allServers.Ivpn.Version,
|
version: allServers.Ivpn.Version,
|
||||||
@@ -167,6 +172,11 @@ func Test_timestamps(t *testing.T) {
|
|||||||
timestamp: allServers.HideMyAss.Timestamp,
|
timestamp: allServers.HideMyAss.Timestamp,
|
||||||
digest: "8f872ac4",
|
digest: "8f872ac4",
|
||||||
},
|
},
|
||||||
|
"Ipvanish": {
|
||||||
|
servers: allServers.Ipvanish.Servers,
|
||||||
|
timestamp: allServers.Ipvanish.Timestamp,
|
||||||
|
digest: "c62dcf98",
|
||||||
|
},
|
||||||
"Ivpn": {
|
"Ivpn": {
|
||||||
servers: allServers.Ivpn.Servers,
|
servers: allServers.Ivpn.Servers,
|
||||||
timestamp: allServers.Ivpn.Timestamp,
|
timestamp: allServers.Ivpn.Timestamp,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const (
|
|||||||
Fastestvpn = "fastestvpn"
|
Fastestvpn = "fastestvpn"
|
||||||
// HideMyAss is a VPN provider.
|
// HideMyAss is a VPN provider.
|
||||||
HideMyAss = "hidemyass"
|
HideMyAss = "hidemyass"
|
||||||
|
// Ipvanish is a VPN provider.
|
||||||
|
Ipvanish = "ipvanish"
|
||||||
// Ivpn is a VPN provider.
|
// Ivpn is a VPN provider.
|
||||||
Ivpn = "ivpn"
|
Ivpn = "ivpn"
|
||||||
// Mullvad is a VPN provider.
|
// Mullvad 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 IpvanishServer 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 *IpvanishServer) String() string {
|
||||||
|
return fmt.Sprintf("{Country: %q, City: %q, Hostname: %q, TCP: %t, UDP: %t, IPs: %s}",
|
||||||
|
s.Country, s.City, s.Hostname, s.TCP, s.UDP, goStringifyIPs(s.IPs))
|
||||||
|
}
|
||||||
|
|
||||||
type IvpnServer struct {
|
type IvpnServer struct {
|
||||||
Country string `json:"country"`
|
Country string `json:"country"`
|
||||||
City string `json:"city"`
|
City string `json:"city"`
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
Ipvanish IpvanishServers `json:"ipvanish"`
|
||||||
Ivpn IvpnServers `json:"ivpn"`
|
Ivpn IvpnServers `json:"ivpn"`
|
||||||
Mullvad MullvadServers `json:"mullvad"`
|
Mullvad MullvadServers `json:"mullvad"`
|
||||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||||
@@ -24,6 +25,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.Ipvanish.Servers) +
|
||||||
len(a.Ivpn.Servers) +
|
len(a.Ivpn.Servers) +
|
||||||
len(a.Mullvad.Servers) +
|
len(a.Mullvad.Servers) +
|
||||||
len(a.Nordvpn.Servers) +
|
len(a.Nordvpn.Servers) +
|
||||||
@@ -54,6 +56,11 @@ type HideMyAssServers struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Servers []HideMyAssServer `json:"servers"`
|
Servers []HideMyAssServer `json:"servers"`
|
||||||
}
|
}
|
||||||
|
type IpvanishServers struct {
|
||||||
|
Version uint16 `json:"version"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Servers []IpvanishServer `json:"servers"`
|
||||||
|
}
|
||||||
type IvpnServers struct {
|
type IvpnServers struct {
|
||||||
Version uint16 `json:"version"`
|
Version uint16 `json:"version"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
|||||||
45
internal/provider/ipvanish/connection.go
Normal file
45
internal/provider/ipvanish/connection.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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 *Ipvanish) GetOpenVPNConnection(selection configuration.ServerSelection) (
|
||||||
|
connection models.OpenVPNConnection, err error) {
|
||||||
|
const port = 443
|
||||||
|
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/ipvanish/filter.go
Normal file
29
internal/provider/ipvanish/filter.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *Ipvanish) filterServers(selection configuration.ServerSelection) (
|
||||||
|
servers []models.IpvanishServer, 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
|
||||||
|
}
|
||||||
65
internal/provider/ipvanish/openvpnconf.go
Normal file
65
internal/provider/ipvanish/openvpnconf.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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 *Ipvanish) BuildConf(connection models.OpenVPNConnection,
|
||||||
|
username string, settings configuration.OpenVPN) (lines []string) {
|
||||||
|
if settings.Cipher == "" {
|
||||||
|
settings.Cipher = constants.AES256cbc
|
||||||
|
}
|
||||||
|
if settings.Auth == "" {
|
||||||
|
settings.Auth = constants.SHA256
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = []string{
|
||||||
|
"client",
|
||||||
|
"dev tun",
|
||||||
|
"nobind",
|
||||||
|
"persist-key",
|
||||||
|
"ping-timer-rem",
|
||||||
|
"tls-exit",
|
||||||
|
|
||||||
|
// Ipvanish specific
|
||||||
|
"comp-lzo",
|
||||||
|
"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",
|
||||||
|
"keysize 256",
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
connection.RemoteLine(),
|
||||||
|
"verify-x509-name " + connection.Hostname, // + " name"
|
||||||
|
"auth " + settings.Auth,
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||||
|
|
||||||
|
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.IpvanishCA)...)
|
||||||
|
|
||||||
|
lines = append(lines, "")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
17
internal/provider/ipvanish/portforward.go
Normal file
17
internal/provider/ipvanish/portforward.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *Ipvanish) 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 Ipvanish")
|
||||||
|
}
|
||||||
19
internal/provider/ipvanish/provider.go
Normal file
19
internal/provider/ipvanish/provider.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ipvanish struct {
|
||||||
|
servers []models.IpvanishServer
|
||||||
|
randSource rand.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(servers []models.IpvanishServer, randSource rand.Source) *Ipvanish {
|
||||||
|
return &Ipvanish{
|
||||||
|
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/ipvanish"
|
||||||
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
"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"
|
||||||
@@ -50,6 +51,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.Ipvanish:
|
||||||
|
return ipvanish.New(allServers.Ipvanish.Servers, randSource)
|
||||||
case constants.Ivpn:
|
case constants.Ivpn:
|
||||||
return ivpn.New(allServers.Ivpn.Servers, randSource)
|
return ivpn.New(allServers.Ivpn.Servers, randSource)
|
||||||
case constants.Mullvad:
|
case constants.Mullvad:
|
||||||
|
|||||||
@@ -36,6 +36,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),
|
||||||
|
Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish),
|
||||||
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
|
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),
|
||||||
@@ -93,6 +94,19 @@ func (s *storage) mergeHideMyAss(hardcoded, persisted models.HideMyAssServers) m
|
|||||||
return persisted
|
return persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storage) mergeIpvanish(hardcoded, persisted models.IpvanishServers) models.IpvanishServers {
|
||||||
|
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||||
|
return hardcoded
|
||||||
|
}
|
||||||
|
versionDiff := hardcoded.Version - persisted.Version
|
||||||
|
if versionDiff > 0 {
|
||||||
|
s.logVersionDiff("Ipvanish", versionDiff)
|
||||||
|
return hardcoded
|
||||||
|
}
|
||||||
|
s.logTimeDiff("Ipvanish", persisted.Timestamp, hardcoded.Timestamp)
|
||||||
|
return persisted
|
||||||
|
}
|
||||||
|
|
||||||
func (s *storage) mergeIvpn(hardcoded, persisted models.IvpnServers) models.IvpnServers {
|
func (s *storage) mergeIvpn(hardcoded, persisted models.IvpnServers) models.IvpnServers {
|
||||||
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.Ipvanish.Servers) +
|
||||||
len(allServers.Ivpn.Servers) +
|
len(allServers.Ivpn.Servers) +
|
||||||
len(allServers.Mullvad.Servers) +
|
len(allServers.Mullvad.Servers) +
|
||||||
len(allServers.Nordvpn.Servers) +
|
len(allServers.Nordvpn.Servers) +
|
||||||
|
|||||||
@@ -8,6 +8,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/ipvanish"
|
||||||
"github.com/qdm12/gluetun/internal/updater/providers/ivpn"
|
"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"
|
||||||
@@ -77,6 +78,26 @@ func (u *updater) updateHideMyAss(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updater) updateIpvanish(ctx context.Context) (err error) {
|
||||||
|
minServers := getMinServers(len(u.servers.Ipvanish.Servers))
|
||||||
|
servers, warnings, err := ipvanish.GetServers(
|
||||||
|
ctx, u.unzipper, u.presolver, minServers)
|
||||||
|
if u.options.CLI {
|
||||||
|
for _, warning := range warnings {
|
||||||
|
u.logger.Warn("Ipvanish: %s", warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if u.options.Stdout {
|
||||||
|
u.println(ipvanish.Stringify(servers))
|
||||||
|
}
|
||||||
|
u.servers.Ipvanish.Timestamp = u.timeNow().Unix()
|
||||||
|
u.servers.Ipvanish.Servers = servers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *updater) updateIvpn(ctx context.Context) (err error) {
|
func (u *updater) updateIvpn(ctx context.Context) (err error) {
|
||||||
minServers := getMinServers(len(u.servers.Ivpn.Servers))
|
minServers := getMinServers(len(u.servers.Ivpn.Servers))
|
||||||
servers, warnings, err := ivpn.GetServers(
|
servers, warnings, err := ivpn.GetServers(
|
||||||
|
|||||||
39
internal/updater/providers/ipvanish/filename.go
Normal file
39
internal/updater/providers/ipvanish/filename.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errCountryCodeUnknown = errors.New("country code is unknown")
|
||||||
|
|
||||||
|
func parseFilename(fileName, hostname string) (
|
||||||
|
country, city string, err error) {
|
||||||
|
const prefix = "ipvanish-"
|
||||||
|
s := strings.TrimPrefix(fileName, prefix)
|
||||||
|
|
||||||
|
const ext = ".ovpn"
|
||||||
|
host := strings.Split(hostname, ".")[0]
|
||||||
|
suffix := "-" + host + ext
|
||||||
|
s = strings.TrimSuffix(s, suffix)
|
||||||
|
|
||||||
|
parts := strings.Split(s, "-")
|
||||||
|
|
||||||
|
countryCodes := constants.CountryCodes()
|
||||||
|
countryCode := strings.ToLower(parts[0])
|
||||||
|
country, ok := countryCodes[countryCode]
|
||||||
|
if !ok {
|
||||||
|
return "", "", fmt.Errorf("%w: %s", errCountryCodeUnknown, countryCode)
|
||||||
|
}
|
||||||
|
country = strings.Title(country)
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
city = strings.Join(parts[1:], " ")
|
||||||
|
city = strings.Title(city)
|
||||||
|
}
|
||||||
|
|
||||||
|
return country, city, nil
|
||||||
|
}
|
||||||
54
internal/updater/providers/ipvanish/filename_test.go
Normal file
54
internal/updater/providers/ipvanish/filename_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseFilename(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fileName string
|
||||||
|
hostname string
|
||||||
|
country string
|
||||||
|
city string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"unknown country code": {
|
||||||
|
fileName: "ipvanish-unknown-host.ovpn",
|
||||||
|
hostname: "host.ipvanish.com",
|
||||||
|
err: errors.New("country code is unknown: unknown"),
|
||||||
|
},
|
||||||
|
"country code only": {
|
||||||
|
fileName: "ipvanish-ca-host.ovpn",
|
||||||
|
hostname: "host.ipvanish.com",
|
||||||
|
country: "Canada",
|
||||||
|
},
|
||||||
|
"country code and city": {
|
||||||
|
fileName: "ipvanish-ca-sao-paulo-host.ovpn",
|
||||||
|
hostname: "host.ipvanish.com",
|
||||||
|
country: "Canada",
|
||||||
|
city: "Sao Paulo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
country, city, err := parseFilename(testCase.fileName, testCase.hostname)
|
||||||
|
|
||||||
|
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.country, country)
|
||||||
|
assert.Equal(t, testCase.city, city)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
58
internal/updater/providers/ipvanish/hosttoserver.go
Normal file
58
internal/updater/providers/ipvanish/hosttoserver.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hostToServer map[string]models.IpvanishServer
|
||||||
|
|
||||||
|
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.IpvanishServer) {
|
||||||
|
servers = make([]models.IpvanishServer, 0, len(hts))
|
||||||
|
for _, server := range hts {
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
return servers
|
||||||
|
}
|
||||||
211
internal/updater/providers/ipvanish/hosttoserver_test.go
Normal file
211
internal/updater/providers/ipvanish/hosttoserver_test.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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.IpvanishServer{
|
||||||
|
Hostname: "host",
|
||||||
|
Country: "country",
|
||||||
|
City: "city",
|
||||||
|
TCP: true,
|
||||||
|
UDP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"extend existing server": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"host": models.IpvanishServer{
|
||||||
|
Hostname: "host",
|
||||||
|
Country: "country",
|
||||||
|
City: "city",
|
||||||
|
TCP: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host: "host",
|
||||||
|
country: "country",
|
||||||
|
city: "city",
|
||||||
|
tcp: false,
|
||||||
|
udp: true,
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"host": models.IpvanishServer{
|
||||||
|
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.IpvanishServer{
|
||||||
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"add IPs to existing server": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"A": models.IpvanishServer{
|
||||||
|
Country: "country",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hostToIPs: map[string][]net.IP{
|
||||||
|
"A": {{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
expectedHTS: hostToServer{
|
||||||
|
"A": models.IpvanishServer{
|
||||||
|
Country: "country",
|
||||||
|
IPs: []net.IP{{1, 2, 3, 4}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"remove server without IP": {
|
||||||
|
initialHTS: hostToServer{
|
||||||
|
"A": models.IpvanishServer{
|
||||||
|
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.IpvanishServer
|
||||||
|
}{
|
||||||
|
"empty host to server": {
|
||||||
|
hts: hostToServer{},
|
||||||
|
servers: []models.IpvanishServer{},
|
||||||
|
},
|
||||||
|
"multiple servers": {
|
||||||
|
hts: hostToServer{
|
||||||
|
"A": {Country: "A"},
|
||||||
|
"B": {Country: "B"},
|
||||||
|
},
|
||||||
|
servers: []models.IpvanishServer{
|
||||||
|
{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/ipvanish/resolve.go
Normal file
36
internal/updater/providers/ipvanish/resolve.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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/ipvanish/resolve_test.go
Normal file
56
internal/updater/providers/ipvanish/resolve_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
92
internal/updater/providers/ipvanish/servers.go
Normal file
92
internal/updater/providers/ipvanish/servers.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// Package ipvanish contains code to obtain the server information
|
||||||
|
// for the Surshark provider.
|
||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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.IpvanishServer, warnings []string, err error) {
|
||||||
|
const url = "https://www.ipvanish.com/software/configs/configs.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
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, 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, err := parseFilename(fileName, hostname)
|
||||||
|
if err != nil {
|
||||||
|
// treat error as warning and go to next file
|
||||||
|
warning := err.Error() + " in " + fileName
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hts.add(hostname, 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
|
||||||
|
}
|
||||||
150
internal/updater/providers/ipvanish/servers_test.go
Normal file
150
internal/updater/providers/ipvanish/servers_test.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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.IpvanishServer
|
||||||
|
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{
|
||||||
|
"ipvanish-CA-City-A-hosta.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{
|
||||||
|
"ipvanish-CA-City-A-hosta.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"),
|
||||||
|
},
|
||||||
|
"filename parsing error": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{
|
||||||
|
"ipvanish-unknown-City-A-hosta.ovpn": []byte("remote hosta"),
|
||||||
|
},
|
||||||
|
warnings: []string{"country code is unknown: unknown in ipvanish-unknown-City-A-hosta.ovpn"},
|
||||||
|
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
minServers: 1,
|
||||||
|
unzipContents: map[string][]byte{
|
||||||
|
"ipvanish-CA-City-A-hosta.ovpn": []byte("remote hosta"),
|
||||||
|
"ipvanish-LU-City-B-hostb.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.IpvanishServer{
|
||||||
|
{Country: "Canada", City: "City A", Hostname: "hosta", UDP: true, IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}},
|
||||||
|
{Country: "Luxembourg", 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.ipvanish.com/software/configs/configs.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/ipvanish/sort.go
Normal file
19
internal/updater/providers/ipvanish/sort.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sortServers(servers []models.IpvanishServer) {
|
||||||
|
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/ipvanish/sort_test.go
Normal file
40
internal/updater/providers/ipvanish/sort_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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.IpvanishServer
|
||||||
|
sortedServers []models.IpvanishServer
|
||||||
|
}{
|
||||||
|
"no server": {},
|
||||||
|
"sorted servers": {
|
||||||
|
initialServers: []models.IpvanishServer{
|
||||||
|
{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.IpvanishServer{
|
||||||
|
{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/ipvanish/string.go
Normal file
14
internal/updater/providers/ipvanish/string.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/models"
|
||||||
|
|
||||||
|
func Stringify(servers []models.IpvanishServer) (s string) {
|
||||||
|
s = "func IpvanishServers() []models.IpvanishServer {\n"
|
||||||
|
s += " return []models.IpvanishServer{\n"
|
||||||
|
for _, server := range servers {
|
||||||
|
s += " " + server.String() + ",\n"
|
||||||
|
}
|
||||||
|
s += " }\n"
|
||||||
|
s += "}"
|
||||||
|
return s
|
||||||
|
}
|
||||||
43
internal/updater/providers/ipvanish/string_test.go
Normal file
43
internal/updater/providers/ipvanish/string_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package ipvanish
|
||||||
|
|
||||||
|
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.IpvanishServer
|
||||||
|
s string
|
||||||
|
}{
|
||||||
|
"no server": {
|
||||||
|
s: `func IpvanishServers() []models.IpvanishServer {
|
||||||
|
return []models.IpvanishServer{
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
"multiple servers": {
|
||||||
|
servers: []models.IpvanishServer{
|
||||||
|
{Country: "A"},
|
||||||
|
{Country: "B"},
|
||||||
|
},
|
||||||
|
s: `func IpvanishServers() []models.IpvanishServer {
|
||||||
|
return []models.IpvanishServer{
|
||||||
|
{Country: "A", City: "", Hostname: "", TCP: false, UDP: false, IPs: []net.IP{}},
|
||||||
|
{Country: "B", City: "", Hostname: "", TCP: false, UDP: false, 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.options.Ipvanish {
|
||||||
|
u.logger.Info("updating Ipvanish servers...")
|
||||||
|
if err := u.updateIpvanish(ctx); err != nil {
|
||||||
|
u.logger.Error(err)
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return allServers, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if u.options.Ivpn {
|
if u.options.Ivpn {
|
||||||
u.logger.Info("updating Ivpn servers...")
|
u.logger.Info("updating Ivpn servers...")
|
||||||
if err := u.updateIvpn(ctx); err != nil {
|
if err := u.updateIvpn(ctx); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user