chore(all): memory and thread safe storage

- settings: get filter choices from storage for settings validation
- updater: update servers to the storage
- storage: minimal deep copying and data duplication
- storage: add merged servers mutex for thread safety
- connection: filter servers in storage
- formatter: format servers to Markdown in storage
- PIA: get server by name from storage directly
- Updater: get servers count from storage directly
- Updater: equality check done in storage, fix #882
This commit is contained in:
Quentin McGaw
2022-06-05 14:58:46 +00:00
parent 1e6b4ed5eb
commit 36b504609b
84 changed files with 1267 additions and 877 deletions

View File

@@ -0,0 +1,66 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/provider/common (interfaces: Storage)
// Package common is a generated GoMock package.
package common
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
settings "github.com/qdm12/gluetun/internal/configuration/settings"
models "github.com/qdm12/gluetun/internal/models"
)
// MockStorage is a mock of Storage interface.
type MockStorage struct {
ctrl *gomock.Controller
recorder *MockStorageMockRecorder
}
// MockStorageMockRecorder is the mock recorder for MockStorage.
type MockStorageMockRecorder struct {
mock *MockStorage
}
// NewMockStorage creates a new mock instance.
func NewMockStorage(ctrl *gomock.Controller) *MockStorage {
mock := &MockStorage{ctrl: ctrl}
mock.recorder = &MockStorageMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
return m.recorder
}
// FilterServers mocks base method.
func (m *MockStorage) FilterServers(arg0 string, arg1 settings.ServerSelection) ([]models.Server, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FilterServers", arg0, arg1)
ret0, _ := ret[0].([]models.Server)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FilterServers indicates an expected call of FilterServers.
func (mr *MockStorageMockRecorder) FilterServers(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterServers", reflect.TypeOf((*MockStorage)(nil).FilterServers), arg0, arg1)
}
// GetServerByName mocks base method.
func (m *MockStorage) GetServerByName(arg0, arg1 string) (models.Server, bool) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetServerByName", arg0, arg1)
ret0, _ := ret[0].(models.Server)
ret1, _ := ret[1].(bool)
return ret0, ret1
}
// GetServerByName indicates an expected call of GetServerByName.
func (mr *MockStorageMockRecorder) GetServerByName(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServerByName", reflect.TypeOf((*MockStorage)(nil).GetServerByName), arg0, arg1)
}

View File

@@ -0,0 +1,5 @@
package common
// Exceptionally, the storage mock is exported since it is used by all
// provider subpackages tests, and it reduces test code duplication a lot.
//go:generate mockgen -destination=mocks.go -package $GOPACKAGE . Storage

View File

@@ -0,0 +1,12 @@
package common
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/models"
)
type Storage interface {
FilterServers(provider string, selection settings.ServerSelection) (
servers []models.Server, err error)
GetServerByName(provider, name string) (server models.Server, ok bool)
}

View File

@@ -2,6 +2,7 @@ package cyberghost
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 443, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Cyberghost,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Cyberghost),
}

View File

@@ -2,6 +2,7 @@ package expressvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(0, 1195, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Expressvpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -1,41 +1,63 @@
package expressvpn
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)
func Test_Provider_GetConnection(t *testing.T) {
t.Parallel()
const provider = providers.Expressvpn
errTest := errors.New("test error")
boolPtr := func(b bool) *bool { return &b }
testCases := map[string]struct {
servers []models.Server
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
filteredServers []models.Server
storageErr error
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
panicMessage string
}{
"no server": {
selection: settings.ServerSelection{}.WithDefaults(providers.Expressvpn),
errWrapped: utils.ErrNoServer,
errMessage: "no server",
"error": {
storageErr: errTest,
errWrapped: errTest,
errMessage: "cannot filter servers: test error",
},
"no filter": {
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
"default OpenVPN TCP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{}.WithDefaults(providers.Expressvpn),
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(true),
},
}.WithDefaults(provider),
panicMessage: "no default OpenVPN TCP port is defined!",
},
"default OpenVPN UDP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(false),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
@@ -43,38 +65,14 @@ func Test_Provider_GetConnection(t *testing.T) {
Protocol: constants.UDP,
},
},
"target IP": {
"default Wireguard port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
}.WithDefaults(providers.Expressvpn),
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1195,
Protocol: constants.UDP,
},
},
"with filter": {
selection: settings.ServerSelection{
Hostnames: []string{"b"},
}.WithDefaults(providers.Expressvpn),
servers: []models.Server{
{Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1195,
Protocol: constants.UDP,
Hostname: "b",
},
VPN: vpn.Wireguard,
}.WithDefaults(provider),
panicMessage: "no default Wireguard port is defined!",
},
}
@@ -82,12 +80,23 @@ func Test_Provider_GetConnection(t *testing.T) {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
storage := common.NewMockStorage(ctrl)
storage.EXPECT().FilterServers(provider, testCase.selection).
Return(testCase.filteredServers, testCase.storageErr)
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
provider := New(storage, randSource)
connection, err := m.GetConnection(testCase.selection)
if testCase.panicMessage != "" {
assert.PanicsWithValue(t, testCase.panicMessage, func() {
_, _ = provider.GetConnection(testCase.selection)
})
return
}
connection, err := provider.GetConnection(testCase.selection)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Expressvpn),
}

View File

@@ -2,6 +2,7 @@ package fastestvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(4443, 4443, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Fastestvpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Fastestvpn),
}

View File

@@ -2,6 +2,7 @@ package hidemyass
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(8080, 553, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.HideMyAss,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.HideMyAss),
}

View File

@@ -2,6 +2,7 @@ package ipvanish
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(0, 443, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Ipvanish,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Ipvanish),
}

View File

@@ -2,6 +2,7 @@ package ivpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 58237) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Ivpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -1,41 +1,67 @@
package ivpn
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)
func Test_Provider_GetConnection(t *testing.T) {
t.Parallel()
const provider = providers.Ivpn
errTest := errors.New("test error")
boolPtr := func(b bool) *bool { return &b }
testCases := map[string]struct {
servers []models.Server
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
filteredServers []models.Server
storageErr error
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
}{
"no server available": {
selection: settings.ServerSelection{}.WithDefaults(providers.Ivpn),
errWrapped: utils.ErrNoServer,
errMessage: "no server",
"error": {
storageErr: errTest,
errWrapped: errTest,
errMessage: "cannot filter servers: test error",
},
"no filter": {
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
"default OpenVPN TCP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{}.WithDefaults(providers.Ivpn),
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(true),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
Port: 443,
Protocol: constants.TCP,
},
},
"default OpenVPN UDP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(false),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
@@ -43,51 +69,36 @@ func Test_Provider_GetConnection(t *testing.T) {
Protocol: constants.UDP,
},
},
"target IP": {
"default Wireguard port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
}.WithDefaults(providers.Ivpn),
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
VPN: vpn.Wireguard,
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Type: vpn.Wireguard,
IP: net.IPv4(1, 1, 1, 1),
Port: 58237,
Protocol: constants.UDP,
},
},
"with filter": {
selection: settings.ServerSelection{
Hostnames: []string{"b"},
}.WithDefaults(providers.Ivpn),
servers: []models.Server{
{Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
Hostname: "b",
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
storage := common.NewMockStorage(ctrl)
storage.EXPECT().FilterServers(provider, testCase.selection).
Return(testCase.filteredServers, testCase.storageErr)
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
provider := New(storage, randSource)
connection, err := m.GetConnection(testCase.selection)
connection, err := provider.GetConnection(testCase.selection)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Ivpn),
}

View File

@@ -2,6 +2,7 @@ package mullvad
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 51820) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Mullvad,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -1,41 +1,67 @@
package mullvad
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)
func Test_Provider_GetConnection(t *testing.T) {
t.Parallel()
const provider = providers.Mullvad
errTest := errors.New("test error")
boolPtr := func(b bool) *bool { return &b }
testCases := map[string]struct {
servers []models.Server
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
filteredServers []models.Server
storageErr error
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
}{
"no server available": {
selection: settings.ServerSelection{}.WithDefaults(providers.Mullvad),
errWrapped: utils.ErrNoServer,
errMessage: "no server",
"error": {
storageErr: errTest,
errWrapped: errTest,
errMessage: "cannot filter servers: test error",
},
"no filter": {
servers: []models.Server{
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
"default OpenVPN TCP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{}.WithDefaults(providers.Mullvad),
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(true),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
Port: 443,
Protocol: constants.TCP,
},
},
"default OpenVPN UDP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(false),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
@@ -43,36 +69,17 @@ func Test_Provider_GetConnection(t *testing.T) {
Protocol: constants.UDP,
},
},
"target IP": {
"default Wireguard port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
}.WithDefaults(providers.Mullvad),
servers: []models.Server{
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
VPN: vpn.Wireguard,
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
},
},
"with filter": {
selection: settings.ServerSelection{
Hostnames: []string{"b"},
}.WithDefaults(providers.Mullvad),
servers: []models.Server{
{VPN: vpn.OpenVPN, UDP: true, Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{VPN: vpn.OpenVPN, UDP: true, Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{VPN: vpn.OpenVPN, UDP: true, Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Hostname: "b",
Type: vpn.Wireguard,
IP: net.IPv4(1, 1, 1, 1),
Port: 51820,
Protocol: constants.UDP,
},
},
@@ -82,12 +89,16 @@ func Test_Provider_GetConnection(t *testing.T) {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
storage := common.NewMockStorage(ctrl)
storage.EXPECT().FilterServers(provider, testCase.selection).
Return(testCase.filteredServers, testCase.storageErr)
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
provider := New(storage, randSource)
connection, err := m.GetConnection(testCase.selection)
connection, err := provider.GetConnection(testCase.selection)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Mullvad),
}

View File

@@ -2,6 +2,7 @@ package nordvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Nordvpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Nordvpn),
}

View File

@@ -2,6 +2,7 @@ package perfectprivacy
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 443, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Perfectprivacy,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Perfectprivacy),
}

View File

@@ -2,6 +2,7 @@ package privado
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(0, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Privado,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Privado),
}

View File

@@ -2,6 +2,7 @@ package privateinternetaccess
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gluetun/internal/provider/utils"
@@ -20,5 +21,6 @@ func (p *Provider) GetConnection(selection settings.ServerSelection) (
defaults.OpenVPNUDPPort = 1197
}
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.PrivateInternetAccess,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -15,25 +15,24 @@ import (
"strings"
"time"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/golibs/format"
)
var (
ErrGatewayIPIsNil = errors.New("gateway IP address is nil")
ErrServerNameEmpty = errors.New("server name is empty")
ErrServerNameNotFound = errors.New("server name not found in servers")
ErrGatewayIPIsNil = errors.New("gateway IP address is nil")
ErrServerNameEmpty = errors.New("server name is empty")
)
// PortForward obtains a VPN server side port forwarded from PIA.
func (p *Provider) PortForward(ctx context.Context, client *http.Client,
logger utils.Logger, gateway net.IP, serverName string) (
port uint16, err error) {
var server models.Server
for _, server = range p.servers {
if server.ServerName == serverName {
break
}
server, ok := p.storage.GetServerByName(providers.PrivateInternetAccess, serverName)
if !ok {
return 0, fmt.Errorf("%w: %s", ErrServerNameNotFound, serverName)
}
if !server.PortForward {

View File

@@ -5,11 +5,11 @@ import (
"time"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
timeNow func() time.Time
// Port forwarding
@@ -17,11 +17,11 @@ type Provider struct {
authFilePath string
}
func New(servers []models.Server, randSource rand.Source,
func New(storage common.Storage, randSource rand.Source,
timeNow func() time.Time) *Provider {
const jsonPortForwardPath = "/gluetun/piaportforward.json"
return &Provider{
servers: servers,
storage: storage,
timeNow: timeNow,
randSource: randSource,
portForwardPath: jsonPortForwardPath,

View File

@@ -2,6 +2,7 @@ package privatevpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Privatevpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Privatevpn),
}

View File

@@ -2,6 +2,7 @@ package protonvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Protonvpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Protonvpn),
}

View File

@@ -50,52 +50,57 @@ type PortForwarder interface {
port uint16, gateway net.IP, serverName string) (err error)
}
func New(provider string, allServers models.AllServers, timeNow func() time.Time) Provider {
serversSlice := allServers.ServersSlice(provider)
type Storage interface {
FilterServers(provider string, selection settings.ServerSelection) (
servers []models.Server, err error)
GetServerByName(provider, name string) (server models.Server, ok bool)
}
func New(provider string, storage Storage, timeNow func() time.Time) Provider {
randSource := rand.NewSource(timeNow().UnixNano())
switch provider {
case providers.Custom:
return custom.New()
case providers.Cyberghost:
return cyberghost.New(serversSlice, randSource)
return cyberghost.New(storage, randSource)
case providers.Expressvpn:
return expressvpn.New(serversSlice, randSource)
return expressvpn.New(storage, randSource)
case providers.Fastestvpn:
return fastestvpn.New(serversSlice, randSource)
return fastestvpn.New(storage, randSource)
case providers.HideMyAss:
return hidemyass.New(serversSlice, randSource)
return hidemyass.New(storage, randSource)
case providers.Ipvanish:
return ipvanish.New(serversSlice, randSource)
return ipvanish.New(storage, randSource)
case providers.Ivpn:
return ivpn.New(serversSlice, randSource)
return ivpn.New(storage, randSource)
case providers.Mullvad:
return mullvad.New(serversSlice, randSource)
return mullvad.New(storage, randSource)
case providers.Nordvpn:
return nordvpn.New(serversSlice, randSource)
return nordvpn.New(storage, randSource)
case providers.Perfectprivacy:
return perfectprivacy.New(serversSlice, randSource)
return perfectprivacy.New(storage, randSource)
case providers.Privado:
return privado.New(serversSlice, randSource)
return privado.New(storage, randSource)
case providers.PrivateInternetAccess:
return privateinternetaccess.New(serversSlice, randSource, timeNow)
return privateinternetaccess.New(storage, randSource, timeNow)
case providers.Privatevpn:
return privatevpn.New(serversSlice, randSource)
return privatevpn.New(storage, randSource)
case providers.Protonvpn:
return protonvpn.New(serversSlice, randSource)
return protonvpn.New(storage, randSource)
case providers.Purevpn:
return purevpn.New(serversSlice, randSource)
return purevpn.New(storage, randSource)
case providers.Surfshark:
return surfshark.New(serversSlice, randSource)
return surfshark.New(storage, randSource)
case providers.Torguard:
return torguard.New(serversSlice, randSource)
return torguard.New(storage, randSource)
case providers.VPNUnlimited:
return vpnunlimited.New(serversSlice, randSource)
return vpnunlimited.New(storage, randSource)
case providers.Vyprvpn:
return vyprvpn.New(serversSlice, randSource)
return vyprvpn.New(storage, randSource)
case providers.Wevpn:
return wevpn.New(serversSlice, randSource)
return wevpn.New(storage, randSource)
case providers.Windscribe:
return windscribe.New(serversSlice, randSource)
return windscribe.New(storage, randSource)
default:
panic("provider " + provider + " is unknown") // should never occur
}

View File

@@ -2,6 +2,7 @@ package purevpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(80, 53, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Purevpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Purevpn),
}

View File

@@ -2,6 +2,7 @@ package surfshark
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(1443, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Surfshark,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Surfshark),
}

View File

@@ -2,6 +2,7 @@ package torguard
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(1912, 1912, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Torguard,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Torguard),
}

View File

@@ -1,7 +1,7 @@
package utils
import (
"errors"
"fmt"
"math/rand"
"github.com/qdm12/gluetun/internal/configuration/settings"
@@ -24,20 +24,20 @@ func NewConnectionDefaults(openvpnTCPPort, openvpnUDPPort,
}
}
var ErrNoServer = errors.New("no server")
type Storage interface {
FilterServers(provider string, selection settings.ServerSelection) (
servers []models.Server, err error)
}
func GetConnection(servers []models.Server,
func GetConnection(provider string,
storage Storage,
selection settings.ServerSelection,
defaults ConnectionDefaults,
randSource rand.Source) (
connection models.Connection, err error) {
if len(servers) == 0 {
return connection, ErrNoServer
}
servers = filterServers(servers, selection)
if len(servers) == 0 {
return connection, noServerFoundError(selection)
servers, err := storage.FilterServers(provider, selection)
if err != nil {
return connection, fmt.Errorf("cannot filter servers: %w", err)
}
protocol := getProtocol(selection)

View File

@@ -1,23 +1,30 @@
package utils
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)
func Test_GetConnection(t *testing.T) {
t.Parallel()
errTest := errors.New("test error")
testCases := map[string]struct {
servers []models.Server
provider string
filteredServers []models.Server
filterError error
serverSelection settings.ServerSelection
defaults ConnectionDefaults
randSource rand.Source
@@ -25,25 +32,13 @@ func Test_GetConnection(t *testing.T) {
errWrapped error
errMessage string
}{
"no server": {
serverSelection: settings.ServerSelection{}.
WithDefaults(providers.Mullvad),
errWrapped: ErrNoServer,
errMessage: "no server",
},
"all servers filtered": {
servers: []models.Server{
{VPN: vpn.Wireguard},
{VPN: vpn.Wireguard},
},
serverSelection: settings.ServerSelection{
VPN: vpn.OpenVPN,
}.WithDefaults(providers.Mullvad),
errWrapped: ErrNoServerFound,
errMessage: "no server found: for VPN openvpn; protocol udp",
"storage filter error": {
filterError: errTest,
errWrapped: errTest,
errMessage: "cannot filter servers: test error",
},
"server without IPs": {
servers: []models.Server{
filteredServers: []models.Server{
{VPN: vpn.OpenVPN, UDP: true},
{VPN: vpn.OpenVPN, UDP: true},
},
@@ -58,7 +53,7 @@ func Test_GetConnection(t *testing.T) {
errMessage: "no connection to pick from",
},
"OpenVPN server with hostname": {
servers: []models.Server{
filteredServers: []models.Server{
{
VPN: vpn.OpenVPN,
UDP: true,
@@ -79,7 +74,7 @@ func Test_GetConnection(t *testing.T) {
},
},
"OpenVPN server with x509": {
servers: []models.Server{
filteredServers: []models.Server{
{
VPN: vpn.OpenVPN,
UDP: true,
@@ -101,7 +96,7 @@ func Test_GetConnection(t *testing.T) {
},
},
"server with IPv4 and IPv6": {
servers: []models.Server{
filteredServers: []models.Server{
{
VPN: vpn.OpenVPN,
UDP: true,
@@ -128,7 +123,7 @@ func Test_GetConnection(t *testing.T) {
},
},
"mixed servers": {
servers: []models.Server{
filteredServers: []models.Server{
{
VPN: vpn.OpenVPN,
UDP: true,
@@ -169,8 +164,14 @@ func Test_GetConnection(t *testing.T) {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
connection, err := GetConnection(testCase.servers,
storage := common.NewMockStorage(ctrl)
storage.EXPECT().
FilterServers(testCase.provider, testCase.serverSelection).
Return(testCase.filteredServers, testCase.filterError)
connection, err := GetConnection(testCase.provider, storage,
testCase.serverSelection, testCase.defaults,
testCase.randSource)

View File

@@ -1,120 +0,0 @@
package utils
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
)
func commaJoin(slice []string) string {
return strings.Join(slice, ", ")
}
var ErrNoServerFound = errors.New("no server found")
func noServerFoundError(selection settings.ServerSelection) (err error) {
var messageParts []string
messageParts = append(messageParts, "VPN "+selection.VPN)
protocol := constants.UDP
if *selection.OpenVPN.TCP {
protocol = constants.TCP
}
messageParts = append(messageParts, "protocol "+protocol)
switch len(selection.Countries) {
case 0:
case 1:
part := "country " + selection.Countries[0]
messageParts = append(messageParts, part)
default:
part := "countries " + commaJoin(selection.Countries)
messageParts = append(messageParts, part)
}
switch len(selection.Regions) {
case 0:
case 1:
part := "region " + selection.Regions[0]
messageParts = append(messageParts, part)
default:
part := "regions " + commaJoin(selection.Regions)
messageParts = append(messageParts, part)
}
switch len(selection.Cities) {
case 0:
case 1:
part := "city " + selection.Cities[0]
messageParts = append(messageParts, part)
default:
part := "cities " + commaJoin(selection.Cities)
messageParts = append(messageParts, part)
}
if *selection.OwnedOnly {
messageParts = append(messageParts, "owned servers only")
}
switch len(selection.ISPs) {
case 0:
case 1:
part := "ISP " + selection.ISPs[0]
messageParts = append(messageParts, part)
default:
part := "ISPs " + commaJoin(selection.ISPs)
messageParts = append(messageParts, part)
}
switch len(selection.Hostnames) {
case 0:
case 1:
part := "hostname " + selection.Hostnames[0]
messageParts = append(messageParts, part)
default:
part := "hostnames " + commaJoin(selection.Hostnames)
messageParts = append(messageParts, part)
}
switch len(selection.Names) {
case 0:
case 1:
part := "name " + selection.Names[0]
messageParts = append(messageParts, part)
default:
part := "names " + commaJoin(selection.Names)
messageParts = append(messageParts, part)
}
switch len(selection.Numbers) {
case 0:
case 1:
part := "server number " + strconv.Itoa(int(selection.Numbers[0]))
messageParts = append(messageParts, part)
default:
serverNumbers := make([]string, len(selection.Numbers))
for i := range selection.Numbers {
serverNumbers[i] = strconv.Itoa(int(selection.Numbers[i]))
}
part := "server numbers " + commaJoin(serverNumbers)
messageParts = append(messageParts, part)
}
if *selection.OpenVPN.PIAEncPreset != "" {
part := "encryption preset " + *selection.OpenVPN.PIAEncPreset
messageParts = append(messageParts, part)
}
if *selection.FreeOnly {
messageParts = append(messageParts, "free tier only")
}
message := "for " + strings.Join(messageParts, "; ")
return fmt.Errorf("%w: %s", ErrNoServerFound, message)
}

View File

@@ -2,6 +2,7 @@ package vpnunlimited
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(0, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.VPNUnlimited,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.VPNUnlimited),
}

View File

@@ -2,6 +2,7 @@ package vyprvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(0, 443, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Vyprvpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Vyprvpn),
}

View File

@@ -2,6 +2,7 @@ package wevpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(1195, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Wevpn,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -1,43 +1,68 @@
package wevpn
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)
func Test_Provider_GetConnection(t *testing.T) {
t.Parallel()
const provider = providers.Wevpn
errTest := errors.New("test error")
boolPtr := func(b bool) *bool { return &b }
testCases := map[string]struct {
servers []models.Server
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
filteredServers []models.Server
storageErr error
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
panicMessage string
}{
"no server available": {
selection: settings.ServerSelection{
VPN: vpn.OpenVPN,
}.WithDefaults(providers.Wevpn),
errWrapped: utils.ErrNoServer,
errMessage: "no server",
"error": {
storageErr: errTest,
errWrapped: errTest,
errMessage: "cannot filter servers: test error",
},
"no filter": {
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
"default OpenVPN TCP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{}.WithDefaults(providers.Wevpn),
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(true),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
Port: 1195,
Protocol: constants.TCP,
},
},
"default OpenVPN UDP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(false),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
@@ -45,38 +70,14 @@ func Test_Provider_GetConnection(t *testing.T) {
Protocol: constants.UDP,
},
},
"target IP": {
"default Wireguard port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
}.WithDefaults(providers.Wevpn),
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
},
},
"with filter": {
selection: settings.ServerSelection{
Hostnames: []string{"b"},
}.WithDefaults(providers.Wevpn),
servers: []models.Server{
{Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Hostname: "b",
Protocol: constants.UDP,
},
VPN: vpn.Wireguard,
}.WithDefaults(provider),
panicMessage: "no default Wireguard port is defined!",
},
}
@@ -84,12 +85,23 @@ func Test_Provider_GetConnection(t *testing.T) {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
storage := common.NewMockStorage(ctrl)
storage.EXPECT().FilterServers(provider, testCase.selection).
Return(testCase.filteredServers, testCase.storageErr)
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
provider := New(storage, randSource)
connection, err := m.GetConnection(testCase.selection)
if testCase.panicMessage != "" {
assert.PanicsWithValue(t, testCase.panicMessage, func() {
_, _ = provider.GetConnection(testCase.selection)
})
return
}
connection, err := provider.GetConnection(testCase.selection)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Wevpn),
}

View File

@@ -2,6 +2,7 @@ package windscribe
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -9,5 +10,6 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 1194) //nolint:gomnd
return utils.GetConnection(p.servers, selection, defaults, p.randSource)
return utils.GetConnection(providers.Windscribe,
p.storage, selection, defaults, p.randSource)
}

View File

@@ -1,41 +1,68 @@
package windscribe
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/stretchr/testify/assert"
)
func Test_Provider_GetConnection(t *testing.T) {
t.Parallel()
const provider = providers.Windscribe
errTest := errors.New("test error")
boolPtr := func(b bool) *bool { return &b }
testCases := map[string]struct {
servers []models.Server
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
filteredServers []models.Server
storageErr error
selection settings.ServerSelection
connection models.Connection
errWrapped error
errMessage string
panicMessage string
}{
"no server available": {
selection: settings.ServerSelection{}.WithDefaults(providers.Windscribe),
errWrapped: utils.ErrNoServer,
errMessage: "no server",
"error": {
storageErr: errTest,
errWrapped: errTest,
errMessage: "cannot filter servers: test error",
},
"no filter": {
servers: []models.Server{
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{VPN: vpn.OpenVPN, UDP: true, IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
"default OpenVPN TCP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{}.WithDefaults(providers.Windscribe),
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(true),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
Port: 443,
Protocol: constants.TCP,
},
},
"default OpenVPN UDP port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
OpenVPN: settings.OpenVPNSelection{
TCP: boolPtr(false),
},
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(1, 1, 1, 1),
@@ -43,49 +70,41 @@ func Test_Provider_GetConnection(t *testing.T) {
Protocol: constants.UDP,
},
},
"target IP": {
"default Wireguard port": {
filteredServers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
},
selection: settings.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
}.WithDefaults(providers.Windscribe),
servers: []models.Server{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
VPN: vpn.Wireguard,
}.WithDefaults(provider),
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Type: vpn.Wireguard,
IP: net.IPv4(1, 1, 1, 1),
Port: 1194,
Protocol: constants.UDP,
},
},
"with filter": {
selection: settings.ServerSelection{
Hostnames: []string{"b"},
}.WithDefaults(providers.Windscribe),
servers: []models.Server{
{Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, VPN: vpn.OpenVPN, UDP: true},
{Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}, VPN: vpn.OpenVPN, UDP: true},
},
connection: models.Connection{
Type: vpn.OpenVPN,
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Hostname: "b",
Protocol: constants.UDP,
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
storage := common.NewMockStorage(ctrl)
storage.EXPECT().FilterServers(provider, testCase.selection).
Return(testCase.filteredServers, testCase.storageErr)
randSource := rand.NewSource(0)
provider := New(testCase.servers, randSource)
provider := New(storage, randSource)
if testCase.panicMessage != "" {
assert.PanicsWithValue(t, testCase.panicMessage, func() {
_, _ = provider.GetConnection(testCase.selection)
})
return
}
connection, err := provider.GetConnection(testCase.selection)
@@ -93,6 +112,7 @@ func Test_Provider_GetConnection(t *testing.T) {
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}
assert.Equal(t, testCase.connection, connection)
})
}

View File

@@ -4,19 +4,19 @@ import (
"math/rand"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
servers []models.Server
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
}
func New(servers []models.Server, randSource rand.Source) *Provider {
func New(storage common.Storage, randSource rand.Source) *Provider {
return &Provider{
servers: servers,
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Windscribe),
}