diff --git a/Dockerfile b/Dockerfile index d245bc8f..04cf89ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,7 +88,9 @@ ENV VPNSP=pia \ # Wireguard WIREGUARD_PRIVATE_KEY= \ WIREGUARD_PRESHARED_KEY= \ + WIREGUARD_PUBLIC_KEY= \ WIREGUARD_ADDRESS= \ + WIREGUARD_ENDPOINT_IP= \ WIREGUARD_PORT= \ WIREGUARD_INTERFACE=wg0 \ # VPN server filtering diff --git a/internal/configuration/custom.go b/internal/configuration/custom.go index 544a18e0..5bd886c5 100644 --- a/internal/configuration/custom.go +++ b/internal/configuration/custom.go @@ -3,6 +3,7 @@ package configuration import ( "errors" "fmt" + "net" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/golibs/params" @@ -16,19 +17,22 @@ var ( func (settings *Provider) readCustom(r reader, vpnType string) (err error) { settings.Name = constants.Custom - if vpnType != constants.OpenVPN { + switch vpnType { + case constants.OpenVPN: + return settings.ServerSelection.OpenVPN.readCustom(r) + case constants.Wireguard: + return settings.ServerSelection.Wireguard.readCustom(r.env) + default: return fmt.Errorf("%w: for VPN type %s", errCustomNotSupported, vpnType) } - - return settings.readCustomOpenVPN(r) } -func (settings *Provider) readCustomOpenVPN(r reader) (err error) { +func (settings *OpenVPNSelection) readCustom(r reader) (err error) { configFile, err := r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue(), params.Compulsory()) if err != nil { return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err) } - settings.ServerSelection.OpenVPN.ConfFile = configFile + settings.ConfFile = configFile // For display and consistency purposes only, // these values are not actually used since the file is re-read @@ -37,7 +41,7 @@ func (settings *Provider) readCustomOpenVPN(r reader) (err error) { if err != nil { return fmt.Errorf("%w: %s", errCustomExtractFromFile, err) } - settings.ServerSelection.OpenVPN.TCP = connection.Protocol == constants.TCP + settings.TCP = connection.Protocol == constants.TCP return nil } @@ -51,3 +55,40 @@ func (settings *OpenVPN) readCustom(r reader) (err error) { return nil } + +func (settings *WireguardSelection) readCustom(env params.Interface) (err error) { + settings.PublicKey, err = env.Get("WIREGUARD_PUBLIC_KEY", + params.CaseSensitiveValue(), params.Compulsory()) + if err != nil { + return fmt.Errorf("environment variable WIREGUARD_PUBLIC_KEY: %w", err) + } + + settings.EndpointIP, err = readWireguardEndpointIP(env) + if err != nil { + return err + } + + settings.EndpointPort, err = env.Port("WIREGUARD_PORT", params.Compulsory()) + if err != nil { + return fmt.Errorf("environment variable WIREGUARD_PORT: %w", err) + } + + return nil +} + +// readWireguardEndpointIP reads and parses the server endpoint IP +// address from the environment variable WIREGUARD_ENDPOINT_IP. +func readWireguardEndpointIP(env params.Interface) (endpointIP net.IP, err error) { + s, err := env.Get("WIREGUARD_ENDPOINT_IP", params.Compulsory()) + if err != nil { + return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w", err) + } + + endpointIP = net.ParseIP(s) + if endpointIP == nil { + return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s", + ErrInvalidIP, s) + } + + return endpointIP, nil +} diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go index 6df395fe..b0c9c4e5 100644 --- a/internal/configuration/provider.go +++ b/internal/configuration/provider.go @@ -106,8 +106,10 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err "privado", "pia", "private internet access", "privatevpn", "protonvpn", "purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"} case constants.Wireguard: - allowedVPNServiceProviders = []string{constants.Mullvad, constants.Windscribe, - constants.Ivpn} + allowedVPNServiceProviders = []string{ + constants.Custom, constants.Ivpn, + constants.Mullvad, constants.Windscribe, + } } vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders, diff --git a/internal/configuration/selection.go b/internal/configuration/selection.go index 352dfa7d..d4224cc7 100644 --- a/internal/configuration/selection.go +++ b/internal/configuration/selection.go @@ -156,12 +156,27 @@ type WireguardSelection struct { // It is optional for Wireguard VPN providers IVPN, Mullvad // and Windscribe, and compulsory for the others EndpointPort uint16 `json:"port,omitempty"` + // PublicKey is the server public key. + // It is only used with VPN providers generating Wireguard + // configurations specific to each server and user. + PublicKey string `json:"publickey,omitempty"` + // EndpointIP is the server endpoint IP address. + // It is only used with VPN providers generating Wireguard + // configurations specific to each server and user. + EndpointIP net.IP `json:"endpoint_ip,omitempty"` } func (settings *WireguardSelection) lines() (lines []string) { lines = append(lines, lastIndent+"Wireguard selection:") - if settings.EndpointPort != 0 { + if settings.PublicKey != "" { + lines = append(lines, indent+lastIndent+"Public key: "+settings.PublicKey) + } + + if settings.EndpointIP != nil { + endpoint := settings.EndpointIP.String() + ":" + fmt.Sprint(settings.EndpointPort) + lines = append(lines, indent+lastIndent+"Server endpoint: "+endpoint) + } else if settings.EndpointPort != 0 { lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.EndpointPort)) } diff --git a/internal/provider/custom/connection.go b/internal/provider/custom/connection.go index 6713c02b..831e6ff5 100644 --- a/internal/provider/custom/connection.go +++ b/internal/provider/custom/connection.go @@ -7,6 +7,7 @@ import ( "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/provider/utils" ) @@ -18,20 +19,40 @@ var ( // GetConnection gets the connection from the OpenVPN configuration file. func (p *Provider) GetConnection(selection configuration.ServerSelection) ( connection models.Connection, err error) { - if selection.VPN != constants.OpenVPN { + switch selection.VPN { + case constants.OpenVPN: + return getOpenVPNConnection(p.extractor, selection) + case constants.Wireguard: + return getWireguardConnection(selection), nil + default: return connection, fmt.Errorf("%w: %s", ErrVPNTypeNotSupported, selection.VPN) } +} - _, connection, err = p.extractor.Data(selection.OpenVPN.ConfFile) +func getOpenVPNConnection(extractor extract.Interface, + selection configuration.ServerSelection) ( + connection models.Connection, err error) { + _, connection, err = extractor.Data(selection.OpenVPN.ConfFile) if err != nil { return connection, fmt.Errorf("%w: %s", ErrExtractConnection, err) } connection.Port = getPort(connection.Port, selection) - return connection, nil } +func getWireguardConnection(selection configuration.ServerSelection) ( + connection models.Connection) { + port := getPort(selection.Wireguard.EndpointPort, selection) + return models.Connection{ + Type: constants.Wireguard, + IP: selection.Wireguard.EndpointIP, + Port: port, + Protocol: constants.UDP, + PubKey: selection.Wireguard.PublicKey, + } +} + // Port found is overridden by custom port set with `PORT` or `WIREGUARD_PORT`. func getPort(foundPort uint16, selection configuration.ServerSelection) (port uint16) { return utils.GetPort(selection, foundPort, foundPort, foundPort)