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

@@ -49,7 +49,7 @@ func (settings *OpenVPNSelection) readMullvad(env params.Env) (err error) {
return err
}
settings.CustomPort, err = readCustomPort(env, settings.TCP,
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
[]uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400})
if err != nil {
return err

View File

@@ -43,7 +43,7 @@ var (
)
func (settings *Provider) read(r reader, vpnType string) error {
err := settings.readVPNServiceProvider(r)
err := settings.readVPNServiceProvider(r, vpnType)
if err != nil {
return err
}
@@ -94,11 +94,17 @@ func (settings *Provider) read(r reader, vpnType string) error {
return nil
}
func (settings *Provider) readVPNServiceProvider(r reader) (err error) {
allowedVPNServiceProviders := []string{
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err error) {
var allowedVPNServiceProviders []string
switch vpnType {
case constants.OpenVPN:
allowedVPNServiceProviders = []string{
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
case constants.Wireguard:
allowedVPNServiceProviders = []string{constants.Mullvad, constants.Windscribe}
}
vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders,
params.Default("private internet access"))
@@ -132,7 +138,7 @@ func readTargetIP(env params.Env) (targetIP net.IP, err error) {
return targetIP, nil
}
func readCustomPort(env params.Env, tcp bool,
func readOpenVPNCustomPort(env params.Env, tcp bool,
allowedTCP, allowedUDP []uint16) (port uint16, err error) {
port, err = readPortOrZero(env, "PORT")
if err != nil {
@@ -147,12 +153,42 @@ func readCustomPort(env params.Env, tcp bool,
return port, nil
}
}
return 0, fmt.Errorf("environment variable PORT: %w: port %d for TCP protocol", ErrInvalidPort, port)
return 0, fmt.Errorf(
"environment variable PORT: %w: port %d for TCP protocol, can only be one of %s",
ErrInvalidPort, port, portsToString(allowedTCP))
}
for i := range allowedUDP {
if allowedUDP[i] == port {
return port, nil
}
}
return 0, fmt.Errorf("environment variable PORT: %w: port %d for UDP protocol", ErrInvalidPort, port)
return 0, fmt.Errorf(
"environment variable PORT: %w: port %d for UDP protocol, can only be one of %s",
ErrInvalidPort, port, portsToString(allowedUDP))
}
func readWireguardCustomPort(env params.Env, allowed []uint16) (port uint16, err error) {
port, err = readPortOrZero(env, "WIREGUARD_PORT")
if err != nil {
return 0, fmt.Errorf("environment variable WIREGUARD_PORT: %w", err)
} else if port == 0 {
return 0, nil
}
for i := range allowed {
if allowed[i] == port {
return port, nil
}
}
return 0, fmt.Errorf(
"environment variable WIREGUARD_PORT: %w: port %d, can only be one of %s",
ErrInvalidPort, port, portsToString(allowed))
}
func portsToString(ports []uint16) string {
slice := make([]string, len(ports))
for i := range ports {
slice[i] = fmt.Sprint(ports[i])
}
return strings.Join(slice, ", ")
}

View File

@@ -24,6 +24,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Cyberghost,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Groups: []string{"group"},
Regions: []string{"a", "El country"},
},
@@ -40,6 +41,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Fastestvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
Countries: []string{"c", "d"},
},
@@ -56,6 +58,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.HideMyAss,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
@@ -74,6 +77,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Ipvanish,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
@@ -92,6 +96,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Ivpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
@@ -110,6 +115,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Mullvad,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
ISPs: []string{"e", "f"},
@@ -132,6 +138,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Nordvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
Numbers: []uint16{1, 2},
},
@@ -148,6 +155,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Privado,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
},
},
@@ -162,6 +170,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Privatevpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
Countries: []string{"c", "d"},
Cities: []string{"e", "f"},
@@ -180,6 +189,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Protonvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Regions: []string{"c", "d"},
Cities: []string{"e", "f"},
@@ -202,6 +212,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.PrivateInternetAccess,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
OpenVPN: OpenVPNSelection{
CustomPort: 1,
@@ -226,6 +237,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Purevpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
Countries: []string{"c", "d"},
Cities: []string{"e", "f"},
@@ -244,6 +256,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Surfshark,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
},
},
@@ -258,6 +271,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Torguard,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e"},
@@ -276,6 +290,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.VPNUnlimited,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
@@ -298,6 +313,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Vyprvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
},
},
@@ -312,6 +328,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Windscribe,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},

View File

@@ -109,12 +109,12 @@ func readIP(env params.Env, key string) (ip net.IP, err error) {
}
func readPortOrZero(env params.Env, key string) (port uint16, err error) {
s, err := env.Get(key)
s, err := env.Get(key, params.Default("0"))
if err != nil {
return 0, err
}
if s == "" || s == "0" {
if s == "0" {
return 0, nil
}

View File

@@ -4,12 +4,13 @@ import (
"fmt"
"net"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
type ServerSelection struct { //nolint:maligned
// Common
VPN string `json:"vpn"`
VPN string `json:"vpn"` // note: this is required
TargetIP net.IP `json:"target_ip,omitempty"`
// TODO comments
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
@@ -39,7 +40,8 @@ type ServerSelection struct { //nolint:maligned
// VPNUnlimited
StreamOnly bool `json:"stream_only"`
OpenVPN OpenVPNSelection `json:"openvpn"`
OpenVPN OpenVPNSelection `json:"openvpn"`
Wireguard WireguardSelection `json:"wireguard"`
}
func (selection ServerSelection) toLines() (lines []string) {
@@ -91,7 +93,11 @@ func (selection ServerSelection) toLines() (lines []string) {
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
}
lines = append(lines, selection.OpenVPN.lines()...)
if selection.VPN == constants.OpenVPN {
lines = append(lines, selection.OpenVPN.lines()...)
} else { // wireguard
lines = append(lines, selection.Wireguard.lines()...)
}
return lines
}
@@ -137,6 +143,20 @@ func (settings *OpenVPNSelection) readProtocolAndPort(env params.Env) (err error
return nil
}
type WireguardSelection struct {
CustomPort uint16 `json:"custom_port"` // Mullvad
}
func (settings *WireguardSelection) lines() (lines []string) {
lines = append(lines, lastIndent+"Wireguard selection:")
if settings.CustomPort != 0 {
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.CustomPort))
}
return lines
}
// PortForwarding contains settings for port forwarding.
type PortForwarding struct {
Enabled bool `json:"enabled"`

View File

@@ -20,6 +20,9 @@ func Test_Settings_lines(t *testing.T) {
Type: constants.OpenVPN,
Provider: Provider{
Name: constants.Mullvad,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
},
},
OpenVPN: OpenVPN{
Version: constants.Openvpn25,

View File

@@ -10,9 +10,10 @@ import (
)
type VPN struct {
Type string `json:"type"`
OpenVPN OpenVPN `json:"openvpn"`
Provider Provider `json:"provider"`
Type string `json:"type"`
OpenVPN OpenVPN `json:"openvpn"`
Wireguard Wireguard `json:"wireguard"`
Provider Provider `json:"provider"`
}
func (settings *VPN) String() string {
@@ -24,7 +25,14 @@ func (settings *VPN) lines() (lines []string) {
lines = append(lines, indent+lastIndent+"Type: "+settings.Type)
for _, line := range settings.OpenVPN.lines() {
var vpnLines []string
switch settings.Type {
case constants.OpenVPN:
vpnLines = settings.OpenVPN.lines()
case constants.Wireguard:
vpnLines = settings.Wireguard.lines()
}
for _, line := range vpnLines {
lines = append(lines, indent+line)
}
@@ -36,13 +44,15 @@ func (settings *VPN) lines() (lines []string) {
}
var (
errReadProviderSettings = errors.New("cannot read provider settings")
errReadOpenVPNSettings = errors.New("cannot read OpenVPN settings")
errReadProviderSettings = errors.New("cannot read provider settings")
errReadOpenVPNSettings = errors.New("cannot read OpenVPN settings")
errReadWireguardSettings = errors.New("cannot read Wireguard settings")
)
func (settings *VPN) read(r reader) (err error) {
vpnType, err := r.env.Inside("VPN_TYPE",
[]string{constants.OpenVPN}, params.Default(constants.OpenVPN))
[]string{constants.OpenVPN, constants.Wireguard},
params.Default(constants.OpenVPN))
if err != nil {
return fmt.Errorf("environment variable VPN_TYPE: %w", err)
}
@@ -54,9 +64,17 @@ func (settings *VPN) read(r reader) (err error) {
}
}
err = settings.OpenVPN.read(r, settings.Provider.Name)
if err != nil {
return fmt.Errorf("%w: %s", errReadOpenVPNSettings, err)
switch settings.Type {
case constants.OpenVPN:
err = settings.OpenVPN.read(r, settings.Provider.Name)
if err != nil {
return fmt.Errorf("%w: %s", errReadOpenVPNSettings, err)
}
case constants.Wireguard:
err = settings.Wireguard.read(r)
if err != nil {
return fmt.Errorf("%w: %s", errReadWireguardSettings, err)
}
}
return nil

View File

@@ -30,7 +30,12 @@ func (settings *Provider) readWindscribe(r reader) (err error) {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readWindscribe(r.env)
err = settings.ServerSelection.OpenVPN.readWindscribe(r.env)
if err != nil {
return err
}
return settings.ServerSelection.Wireguard.readWindscribe(r.env)
}
func (settings *OpenVPNSelection) readWindscribe(env params.Env) (err error) {
@@ -39,7 +44,7 @@ func (settings *OpenVPNSelection) readWindscribe(env params.Env) (err error) {
return err
}
settings.CustomPort, err = readCustomPort(env, settings.TCP,
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
[]uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
[]uint16{53, 80, 123, 443, 1194, 54783})
if err != nil {
@@ -48,3 +53,13 @@ func (settings *OpenVPNSelection) readWindscribe(env params.Env) (err error) {
return nil
}
func (settings *WireguardSelection) readWindscribe(env params.Env) (err error) {
settings.CustomPort, err = readWireguardCustomPort(env,
[]uint16{53, 80, 123, 443, 1194, 65142})
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,73 @@
package configuration
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/params"
)
// Wireguard contains settings to configure the Wireguard client.
type Wireguard struct {
PrivateKey string `json:"privatekey"`
PreSharedKey string `json:"presharedkey"`
Address *net.IPNet `json:"address"`
Interface string `json:"interface"`
}
func (settings *Wireguard) String() string {
return strings.Join(settings.lines(), "\n")
}
func (settings *Wireguard) lines() (lines []string) {
lines = append(lines, lastIndent+"Wireguard:")
lines = append(lines, indent+lastIndent+"Network interface: "+settings.Interface)
if settings.PrivateKey != "" {
lines = append(lines, indent+lastIndent+"Private key is set")
}
if settings.PreSharedKey != "" {
lines = append(lines, indent+lastIndent+"Pre-shared key is set")
}
if settings.Address != nil {
lines = append(lines, indent+lastIndent+"Address: "+settings.Address.String())
}
return lines
}
func (settings *Wireguard) read(r reader) (err error) {
settings.PrivateKey, err = r.env.Get("WIREGUARD_PRIVATE_KEY",
params.CaseSensitiveValue(), params.Unset(), params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_PRIVATE_KEY: %w", err)
}
settings.PreSharedKey, err = r.env.Get("WIREGUARD_PRESHARED_KEY",
params.CaseSensitiveValue(), params.Unset())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_PRESHARED_KEY: %w", err)
}
addressString, err := r.env.Get("WIREGUARD_ADDRESS", params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
}
ip, ipNet, err := net.ParseCIDR(addressString)
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
}
ipNet.IP = ip
settings.Address = ipNet
settings.Interface, err = r.env.Get("WIREGUARD_INTERFACE", params.Default("wg0"))
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_INTERFACE: %w", err)
}
return nil
}