feat(vpn): rotate filtered servers on internal vpn restarts

- Fix #290
This commit is contained in:
Quentin McGaw
2026-05-04 03:28:48 +00:00
parent 4b819b4dbb
commit fed09562e5
57 changed files with 345 additions and 220 deletions
+123 -12
View File
@@ -1,26 +1,137 @@
package utils
import (
"math/rand"
"net/netip"
"testing"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
)
func Test_pickRandomConnection(t *testing.T) {
func Test_ConnectionPicker_pickConnection(t *testing.T) {
t.Parallel()
connections := []models.Connection{
{Port: 1}, {Port: 2}, {Port: 3}, {Port: 4},
picker := NewConnectionPicker()
poolA := []models.Connection{
{Port: 1}, {Port: 2}, {Port: 3},
}
source := rand.NewSource(0)
connection := picker.pickConnection(poolA)
assert.Equal(t, models.Connection{Port: 1}, connection)
connection := pickRandomConnection(connections, source)
assert.Equal(t, models.Connection{Port: 3}, connection)
connection = pickRandomConnection(connections, source)
assert.Equal(t, models.Connection{Port: 3}, connection)
connection = pickRandomConnection(connections, source)
connection = picker.pickConnection(poolA)
assert.Equal(t, models.Connection{Port: 2}, connection)
connection = picker.pickConnection(poolA)
assert.Equal(t, models.Connection{Port: 3}, connection)
connection = picker.pickConnection(poolA)
assert.Equal(t, models.Connection{Port: 1}, connection)
poolB := []models.Connection{
{Port: 10}, {Port: 20},
}
connection = picker.pickConnection(poolB)
assert.Equal(t, models.Connection{Port: 10}, connection)
connection = picker.pickConnection(poolB)
assert.Equal(t, models.Connection{Port: 20}, connection)
connection = picker.pickConnection(poolB)
assert.Equal(t, models.Connection{Port: 10}, connection)
}
func Test_pickConnection(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
connections []models.Connection
selection settings.ServerSelection
connection1 models.Connection
connection2 models.Connection
errMessage string
}{
"empty_connections": {
errMessage: "no connection to pick from",
},
"openvpn_cycles": {
connections: []models.Connection{
{Type: vpn.OpenVPN, Port: 1, Hostname: "one"},
{Type: vpn.OpenVPN, Port: 2, Hostname: "two"},
},
selection: settings.ServerSelection{VPN: vpn.OpenVPN},
connection1: models.Connection{
Type: vpn.OpenVPN, Port: 1,
Hostname: "one",
},
connection2: models.Connection{
Type: vpn.OpenVPN, Port: 2,
Hostname: "two",
},
},
"openvpn_endpoint_ip_overrides_cycle_pick": {
connections: []models.Connection{
{Type: vpn.OpenVPN, Hostname: "one", IP: netip.AddrFrom4([4]byte{1, 1, 1, 1})},
{Type: vpn.OpenVPN, Hostname: "two", IP: netip.AddrFrom4([4]byte{2, 2, 2, 2})},
},
selection: settings.ServerSelection{
VPN: vpn.OpenVPN,
OpenVPN: settings.OpenVPNSelection{
EndpointIP: netip.AddrFrom4([4]byte{9, 9, 9, 9}),
},
},
connection1: models.Connection{
Type: vpn.OpenVPN, Hostname: "one",
IP: netip.AddrFrom4([4]byte{9, 9, 9, 9}),
},
connection2: models.Connection{
Type: vpn.OpenVPN, Hostname: "two",
IP: netip.AddrFrom4([4]byte{9, 9, 9, 9}),
},
},
"wireguard_endpoint_ip_picks_target": {
connections: []models.Connection{
{Type: vpn.Wireguard, Hostname: "one", IP: netip.AddrFrom4([4]byte{1, 1, 1, 1})},
{Type: vpn.Wireguard, Hostname: "two", IP: netip.AddrFrom4([4]byte{2, 2, 2, 2})},
},
selection: settings.ServerSelection{
VPN: vpn.Wireguard,
Wireguard: settings.WireguardSelection{
EndpointIP: netip.AddrFrom4([4]byte{2, 2, 2, 2}),
},
},
connection1: models.Connection{
Type: vpn.Wireguard, Hostname: "two",
IP: netip.AddrFrom4([4]byte{2, 2, 2, 2}),
},
connection2: models.Connection{
Type: vpn.Wireguard, Hostname: "two",
IP: netip.AddrFrom4([4]byte{2, 2, 2, 2}),
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
connPicker := NewConnectionPicker()
connection, err := pickConnection(testCase.connections,
testCase.selection, connPicker)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
assert.Equal(t, models.Connection{}, connection)
return
}
assert.NoError(t, err)
assert.Equal(t, testCase.connection1, connection)
connection, err = pickConnection(testCase.connections,
testCase.selection, connPicker)
assert.NoError(t, err)
assert.Equal(t, testCase.connection2, connection)
})
}
}