225 lines
7.5 KiB
Go
225 lines
7.5 KiB
Go
package settings
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"regexp"
|
|
|
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
|
"github.com/qdm12/gosettings"
|
|
"github.com/qdm12/gosettings/validate"
|
|
"github.com/qdm12/gotree"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
)
|
|
|
|
// Wireguard contains settings to configure the Wireguard client.
|
|
type Wireguard struct {
|
|
// PrivateKey is the Wireguard client peer private key.
|
|
// It cannot be nil in the internal state.
|
|
PrivateKey *string `json:"private_key"`
|
|
// PreSharedKey is the Wireguard pre-shared key.
|
|
// It can be the empty string to indicate there
|
|
// is no pre-shared key.
|
|
// It cannot be nil in the internal state.
|
|
PreSharedKey *string `json:"pre_shared_key"`
|
|
// Addresses are the Wireguard interface addresses.
|
|
Addresses []netip.Prefix `json:"addresses"`
|
|
// AllowedIPs are the Wireguard allowed IPs.
|
|
// If left unset, they default to "0.0.0.0/0"
|
|
// and, if IPv6 is supported, "::0".
|
|
AllowedIPs []netip.Prefix `json:"allowed_ips"`
|
|
// Interface is the name of the Wireguard interface
|
|
// to create. It cannot be the empty string in the
|
|
// internal state.
|
|
Interface string `json:"interface"`
|
|
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
|
// It cannot be zero in the internal state, and defaults to
|
|
// 1400. Note it is not the wireguard-go MTU default of 1420
|
|
// because this impacts bandwidth a lot on some VPN providers,
|
|
// see https://github.com/qdm12/gluetun/issues/1650.
|
|
MTU uint16 `json:"mtu"`
|
|
// Implementation is the Wireguard implementation to use.
|
|
// It can be "auto", "userspace" or "kernelspace".
|
|
// It defaults to "auto" and cannot be the empty string
|
|
// in the internal state.
|
|
Implementation string `json:"implementation"`
|
|
}
|
|
|
|
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
|
|
|
// Validate validates Wireguard settings.
|
|
// It should only be ran if the VPN type chosen is Wireguard.
|
|
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
|
|
if !helpers.IsOneOf(vpnProvider,
|
|
providers.Airvpn,
|
|
providers.Custom,
|
|
providers.Ivpn,
|
|
providers.Mullvad,
|
|
providers.Nordvpn,
|
|
providers.Surfshark,
|
|
providers.Windscribe,
|
|
) {
|
|
// do not validate for VPN provider not supporting Wireguard
|
|
return nil
|
|
}
|
|
|
|
// Validate PrivateKey
|
|
if *w.PrivateKey == "" {
|
|
return fmt.Errorf("%w", ErrWireguardPrivateKeyNotSet)
|
|
}
|
|
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
|
if err != nil {
|
|
err = fmt.Errorf("private key is not valid: %w", err)
|
|
if vpnProvider == providers.Nordvpn &&
|
|
err.Error() == "wgtypes: incorrect key size: 48" {
|
|
err = fmt.Errorf("%w - you might be using your access token instead of the Wireguard private key", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if vpnProvider == providers.Airvpn {
|
|
if *w.PreSharedKey == "" {
|
|
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
|
|
}
|
|
}
|
|
|
|
// Validate PreSharedKey
|
|
if *w.PreSharedKey != "" { // Note: this is optional
|
|
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
|
if err != nil {
|
|
return fmt.Errorf("pre-shared key is not valid: %w", err)
|
|
}
|
|
}
|
|
|
|
// Validate Addresses
|
|
if len(w.Addresses) == 0 {
|
|
return fmt.Errorf("%w", ErrWireguardInterfaceAddressNotSet)
|
|
}
|
|
for i, ipNet := range w.Addresses {
|
|
if !ipNet.IsValid() {
|
|
return fmt.Errorf("%w: for address at index %d",
|
|
ErrWireguardInterfaceAddressNotSet, i)
|
|
}
|
|
|
|
if !ipv6Supported && ipNet.Addr().Is6() {
|
|
return fmt.Errorf("%w: address %s",
|
|
ErrWireguardInterfaceAddressIPv6, ipNet.String())
|
|
}
|
|
}
|
|
|
|
// Validate AllowedIPs
|
|
// WARNING: do not check for IPv6 networks in the allowed IPs,
|
|
// the wireguard code will take care to ignore it.
|
|
if len(w.AllowedIPs) == 0 {
|
|
return fmt.Errorf("%w", ErrWireguardAllowedIPsNotSet)
|
|
}
|
|
for i, allowedIP := range w.AllowedIPs {
|
|
if !allowedIP.IsValid() {
|
|
return fmt.Errorf("%w: for allowed ip %d of %d",
|
|
ErrWireguardAllowedIPNotSet, i+1, len(w.AllowedIPs))
|
|
}
|
|
}
|
|
|
|
// Validate interface
|
|
if !regexpInterfaceName.MatchString(w.Interface) {
|
|
return fmt.Errorf("%w: '%s' does not match regex '%s'",
|
|
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
|
}
|
|
|
|
validImplementations := []string{"auto", "userspace", "kernelspace"}
|
|
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
|
|
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *Wireguard) copy() (copied Wireguard) {
|
|
return Wireguard{
|
|
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
|
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
|
Addresses: gosettings.CopySlice(w.Addresses),
|
|
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
|
|
Interface: w.Interface,
|
|
MTU: w.MTU,
|
|
Implementation: w.Implementation,
|
|
}
|
|
}
|
|
|
|
func (w *Wireguard) mergeWith(other Wireguard) {
|
|
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
|
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
|
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
|
|
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
|
|
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
|
|
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
|
|
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
|
|
}
|
|
|
|
func (w *Wireguard) overrideWith(other Wireguard) {
|
|
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
|
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
|
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
|
|
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
|
|
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
|
|
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
|
|
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
|
|
}
|
|
|
|
func (w *Wireguard) setDefaults(vpnProvider string) {
|
|
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
|
|
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
|
|
if vpnProvider == providers.Nordvpn {
|
|
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
|
|
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
|
|
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
|
|
}
|
|
defaultAllowedIPs := []netip.Prefix{
|
|
netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
}
|
|
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
|
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
|
|
const defaultMTU = 1400
|
|
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
|
|
w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
|
|
}
|
|
|
|
func (w Wireguard) String() string {
|
|
return w.toLinesNode().String()
|
|
}
|
|
|
|
func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|
node = gotree.New("Wireguard settings:")
|
|
|
|
if *w.PrivateKey != "" {
|
|
s := gosettings.ObfuscateKey(*w.PrivateKey)
|
|
node.Appendf("Private key: %s", s)
|
|
}
|
|
|
|
if *w.PreSharedKey != "" {
|
|
s := gosettings.ObfuscateKey(*w.PreSharedKey)
|
|
node.Appendf("Pre-shared key: %s", s)
|
|
}
|
|
|
|
addressesNode := node.Appendf("Interface addresses:")
|
|
for _, address := range w.Addresses {
|
|
addressesNode.Appendf(address.String())
|
|
}
|
|
|
|
allowedIPsNode := node.Appendf("Allowed IPs:")
|
|
for _, allowedIP := range w.AllowedIPs {
|
|
allowedIPsNode.Appendf(allowedIP.String())
|
|
}
|
|
|
|
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
|
interfaceNode.Appendf("MTU: %d", w.MTU)
|
|
|
|
if w.Implementation != "auto" {
|
|
node.Appendf("Implementation: %s", w.Implementation)
|
|
}
|
|
|
|
return node
|
|
}
|