Wireguard support for Mullvad and Windscribe (#565)

- `internal/wireguard` client package with unit tests
- Implementation works with kernel space or user space if unavailable
- `WIREGUARD_PRIVATE_KEY`
- `WIREGUARD_ADDRESS`
- `WIREGUARD_PRESHARED_KEY`
- `WIREGUARD_PORT`
- `internal/netlink` package used by `internal/wireguard`
This commit is contained in:
Quentin McGaw
2021-08-22 14:58:39 -07:00
committed by GitHub
parent 0bfd58a3f5
commit 614eb10d67
70 changed files with 13595 additions and 148 deletions

View File

@@ -9,16 +9,8 @@ import (
func (w *Windscribe) GetConnection(selection configuration.ServerSelection) (
connection models.Connection, err error) {
protocol := constants.UDP
var port uint16 = 443
if selection.OpenVPN.TCP {
protocol = constants.TCP
port = 1194
}
if selection.OpenVPN.CustomPort > 0 {
port = selection.OpenVPN.CustomPort
}
port := getPort(selection)
protocol := getProtocol(selection)
servers, err := w.filterServers(selection)
if err != nil {
@@ -34,6 +26,7 @@ func (w *Windscribe) GetConnection(selection configuration.ServerSelection) (
Port: port,
Protocol: protocol,
Hostname: server.OvpnX509,
PubKey: server.WgPubKey,
}
connections = append(connections, connection)
}
@@ -45,3 +38,33 @@ func (w *Windscribe) GetConnection(selection configuration.ServerSelection) (
return utils.PickRandomConnection(connections, w.randSource), nil
}
func getPort(selection configuration.ServerSelection) (port uint16) {
switch selection.VPN {
case constants.Wireguard:
customPort := selection.Wireguard.CustomPort
if customPort > 0 {
return customPort
}
const defaultPort = 1194
return defaultPort
default: // OpenVPN
customPort := selection.OpenVPN.CustomPort
if customPort > 0 {
return customPort
}
port = 1194
if selection.OpenVPN.TCP {
port = 443
}
return port
}
}
func getProtocol(selection configuration.ServerSelection) (protocol string) {
protocol = constants.UDP
if selection.VPN == constants.OpenVPN && selection.OpenVPN.TCP {
protocol = constants.TCP
}
return protocol
}

View File

@@ -0,0 +1,204 @@
package windscribe
import (
"errors"
"math/rand"
"net"
"testing"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Windscribe_GetConnection(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
servers []models.WindscribeServer
selection configuration.ServerSelection
connection models.Connection
err error
}{
"no server available": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
},
err: errors.New("no server found: for VPN openvpn; protocol udp"),
},
"no filter": {
servers: []models.WindscribeServer{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
IP: net.IPv4(1, 1, 1, 1),
Port: 1194,
Protocol: constants.UDP,
},
},
"target IP": {
selection: configuration.ServerSelection{
TargetIP: net.IPv4(2, 2, 2, 2),
},
servers: []models.WindscribeServer{
{IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
},
},
"with filter": {
selection: configuration.ServerSelection{
Hostnames: []string{"b"},
},
servers: []models.WindscribeServer{
{Hostname: "a", IPs: []net.IP{net.IPv4(1, 1, 1, 1)}},
{Hostname: "b", IPs: []net.IP{net.IPv4(2, 2, 2, 2)}},
{Hostname: "a", IPs: []net.IP{net.IPv4(3, 3, 3, 3)}},
},
connection: models.Connection{
IP: net.IPv4(2, 2, 2, 2),
Port: 1194,
Protocol: constants.UDP,
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
connection, err := m.GetConnection(testCase.selection)
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.connection, connection)
})
}
}
func Test_getPort(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
selection configuration.ServerSelection
port uint16
}{
"default": {
port: 1194,
},
"OpenVPN UDP": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
},
port: 1194,
},
"OpenVPN TCP": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
OpenVPN: configuration.OpenVPNSelection{
TCP: true,
},
},
port: 443,
},
"OpenVPN custom port": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
OpenVPN: configuration.OpenVPNSelection{
CustomPort: 1234,
},
},
port: 1234,
},
"Wireguard": {
selection: configuration.ServerSelection{
VPN: constants.Wireguard,
},
port: 1194,
},
"Wireguard custom port": {
selection: configuration.ServerSelection{
VPN: constants.Wireguard,
Wireguard: configuration.WireguardSelection{
CustomPort: 1234,
},
},
port: 1234,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
port := getPort(testCase.selection)
assert.Equal(t, testCase.port, port)
})
}
}
func Test_getProtocol(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
selection configuration.ServerSelection
protocol string
}{
"default": {
protocol: constants.UDP,
},
"OpenVPN UDP": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
},
protocol: constants.UDP,
},
"OpenVPN TCP": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
OpenVPN: configuration.OpenVPNSelection{
TCP: true,
},
},
protocol: constants.TCP,
},
"Wireguard": {
selection: configuration.ServerSelection{
VPN: constants.Wireguard,
},
protocol: constants.UDP,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
protocol := getProtocol(testCase.selection)
assert.Equal(t, testCase.protocol, protocol)
})
}
}

View File

@@ -11,6 +11,7 @@ func (w *Windscribe) filterServers(selection configuration.ServerSelection) (
for _, server := range w.servers {
switch {
case
server.VPN != selection.VPN,
utils.FilterByPossibilities(server.Region, selection.Regions),
utils.FilterByPossibilities(server.City, selection.Cities),
utils.FilterByPossibilities(server.Hostname, selection.Hostnames):

View File

@@ -0,0 +1,117 @@
package windscribe
import (
"errors"
"math/rand"
"testing"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Windscribe_filterServers(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
servers []models.WindscribeServer
selection configuration.ServerSelection
filtered []models.WindscribeServer
err error
}{
"no server available": {
selection: configuration.ServerSelection{
VPN: constants.OpenVPN,
},
err: errors.New("no server found: for VPN openvpn; protocol udp"),
},
"no filter": {
servers: []models.WindscribeServer{
{Hostname: "a"},
{Hostname: "b"},
{Hostname: "c"},
},
filtered: []models.WindscribeServer{
{Hostname: "a"},
{Hostname: "b"},
{Hostname: "c"},
},
},
"filter OpenVPN out": {
selection: configuration.ServerSelection{
VPN: constants.Wireguard,
},
servers: []models.WindscribeServer{
{VPN: constants.OpenVPN, Hostname: "a"},
{VPN: constants.Wireguard, Hostname: "b"},
{VPN: constants.OpenVPN, Hostname: "c"},
},
filtered: []models.WindscribeServer{
{VPN: constants.Wireguard, Hostname: "b"},
},
},
"filter by region": {
selection: configuration.ServerSelection{
Regions: []string{"b"},
},
servers: []models.WindscribeServer{
{Region: "a"},
{Region: "b"},
{Region: "c"},
},
filtered: []models.WindscribeServer{
{Region: "b"},
},
},
"filter by city": {
selection: configuration.ServerSelection{
Cities: []string{"b"},
},
servers: []models.WindscribeServer{
{City: "a"},
{City: "b"},
{City: "c"},
},
filtered: []models.WindscribeServer{
{City: "b"},
},
},
"filter by hostname": {
selection: configuration.ServerSelection{
Hostnames: []string{"b"},
},
servers: []models.WindscribeServer{
{Hostname: "a"},
{Hostname: "b"},
{Hostname: "c"},
},
filtered: []models.WindscribeServer{
{Hostname: "b"},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
randSource := rand.NewSource(0)
m := New(testCase.servers, randSource)
servers, err := m.filterServers(testCase.selection)
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.filtered, servers)
})
}
}