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:
@@ -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
|
||||
|
||||
@@ -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, ", ")
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
73
internal/configuration/wireguard.go
Normal file
73
internal/configuration/wireguard.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user