diff --git a/internal/configuration/settings/openvpnselection.go b/internal/configuration/settings/openvpnselection.go index 4f681c9f..1ff2d8ac 100644 --- a/internal/configuration/settings/openvpnselection.go +++ b/internal/configuration/settings/openvpnselection.go @@ -2,6 +2,7 @@ package settings import ( "fmt" + "net/netip" "strings" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" @@ -24,6 +25,12 @@ type OpenVPNSelection struct { // and can be udp or tcp. It cannot be the empty string // in the internal state. Protocol string `json:"protocol"` + // EndpointIP is the server endpoint IP address. + // If set, it overrides any IP address from the picked + // built-in server connection. To indicate it should + // not be used, it should be set to [netip.IPv4Unspecified]. + // It can never be the zero value in the internal state. + EndpointIP netip.Addr `json:"endpoint_ip"` // CustomPort is the OpenVPN server endpoint port. // It can be set to 0 to indicate no custom port should // be used. It cannot be nil in the internal state. @@ -142,6 +149,7 @@ func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) { return OpenVPNSelection{ ConfFile: gosettings.CopyPointer(o.ConfFile), Protocol: o.Protocol, + EndpointIP: o.EndpointIP, CustomPort: gosettings.CopyPointer(o.CustomPort), PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset), } @@ -151,12 +159,14 @@ func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) { o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) o.Protocol = gosettings.OverrideWithComparable(o.Protocol, other.Protocol) o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort) + o.EndpointIP = gosettings.OverrideWithValidator(o.EndpointIP, other.EndpointIP) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) } func (o *OpenVPNSelection) setDefaults(vpnProvider string) { o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "") o.Protocol = gosettings.DefaultComparable(o.Protocol, constants.UDP) + o.EndpointIP = gosettings.DefaultValidator(o.EndpointIP, netip.IPv4Unspecified()) o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0) var defaultEncPreset string @@ -174,6 +184,10 @@ func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) { node = gotree.New("OpenVPN server selection settings:") node.Appendf("Protocol: %s", strings.ToUpper(o.Protocol)) + if !o.EndpointIP.IsUnspecified() { + node.Appendf("Endpoint IP address: %s", o.EndpointIP) + } + if *o.CustomPort != 0 { node.Appendf("Custom port: %d", *o.CustomPort) } @@ -194,6 +208,12 @@ func (o *OpenVPNSelection) read(r *reader.Reader) (err error) { o.Protocol = r.String("OPENVPN_PROTOCOL", reader.RetroKeys("PROTOCOL")) + o.EndpointIP, err = r.NetipAddr("OPENVPN_ENDPOINT_IP", + reader.RetroKeys("OPENVPN_TARGET_IP", "VPN_ENDPOINT_IP")) + if err != nil { + return err + } + o.CustomPort, err = r.Uint16Ptr("OPENVPN_ENDPOINT_PORT", reader.RetroKeys("PORT", "OPENVPN_PORT", "VPN_ENDPOINT_PORT")) if err != nil { diff --git a/internal/configuration/settings/serverselection.go b/internal/configuration/settings/serverselection.go index fc3379e1..c1761ae5 100644 --- a/internal/configuration/settings/serverselection.go +++ b/internal/configuration/settings/serverselection.go @@ -3,7 +3,6 @@ package settings import ( "errors" "fmt" - "net/netip" "strings" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" @@ -22,12 +21,6 @@ type ServerSelection struct { //nolint:maligned // or 'wireguard'. It cannot be the empty string // in the internal state. VPN string `json:"vpn"` - // TargetIP is the server endpoint IP address to use. - // It will override any IP address from the picked - // built-in server. It cannot be the empty value in the internal - // state, and can be set to the unspecified address to indicate - // there is not target IP address to use. - TargetIP netip.Addr `json:"target_ip"` // Countries is the list of countries to filter VPN servers with. Countries []string `json:"countries"` // Categories is the list of categories to filter VPN servers with. @@ -299,7 +292,6 @@ func validateFeatureFilters(settings ServerSelection, vpnServiceProvider string) func (ss *ServerSelection) copy() (copied ServerSelection) { return ServerSelection{ VPN: ss.VPN, - TargetIP: ss.TargetIP, Countries: gosettings.CopySlice(ss.Countries), Categories: gosettings.CopySlice(ss.Categories), Regions: gosettings.CopySlice(ss.Regions), @@ -323,7 +315,6 @@ func (ss *ServerSelection) copy() (copied ServerSelection) { func (ss *ServerSelection) overrideWith(other ServerSelection) { ss.VPN = gosettings.OverrideWithComparable(ss.VPN, other.VPN) - ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP) ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries) ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories) ss.Regions = gosettings.OverrideWithSlice(ss.Regions, other.Regions) @@ -346,7 +337,6 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) { func (ss *ServerSelection) setDefaults(vpnProvider string, portForwardingEnabled bool) { ss.VPN = gosettings.DefaultComparable(ss.VPN, vpn.OpenVPN) - ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified()) ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false) ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false) ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false) @@ -371,9 +361,6 @@ func (ss ServerSelection) String() string { func (ss ServerSelection) toLinesNode() (node *gotree.Node) { node = gotree.New("Server selection settings:") node.Appendf("VPN type: %s", ss.VPN) - if !ss.TargetIP.IsUnspecified() { - node.Appendf("Target IP address: %s", ss.TargetIP) - } if len(ss.Countries) > 0 { node.Appendf("Countries: %s", strings.Join(ss.Countries, ", ")) @@ -464,12 +451,6 @@ func (ss *ServerSelection) read(r *reader.Reader, ) (err error) { ss.VPN = vpnType - ss.TargetIP, err = r.NetipAddr("OPENVPN_ENDPOINT_IP", - reader.RetroKeys("OPENVPN_TARGET_IP", "VPN_ENDPOINT_IP")) - if err != nil { - return err - } - countriesRetroKeys := []string{"COUNTRY"} if vpnProvider == providers.Cyberghost { countriesRetroKeys = append(countriesRetroKeys, "REGION") diff --git a/internal/configuration/settings/wireguardselection.go b/internal/configuration/settings/wireguardselection.go index 2f4bf115..5da8a8fb 100644 --- a/internal/configuration/settings/wireguardselection.go +++ b/internal/configuration/settings/wireguardselection.go @@ -14,11 +14,11 @@ import ( type WireguardSelection struct { // EndpointIP is the server endpoint IP address. - // It is only used with VPN providers generating Wireguard - // configurations specific to each server and user. - // To indicate it should not be used, it should be set - // to netip.IPv4Unspecified(). It can never be the zero value - // in the internal state. + // It is notably required with the custom provider. + // Otherwise it overrides any IP address from the picked + // built-in server connection. To indicate it should + // not be used, it should be set to [netip.IPv4Unspecified]. + // It can never be the zero value in the internal state. EndpointIP netip.Addr `json:"endpoint_ip"` // EndpointPort is a the server port to use for the VPN server. // It is optional for VPN providers IVPN, Mullvad, Surfshark diff --git a/internal/provider/utils/pick.go b/internal/provider/utils/pick.go index 74b01be9..40c34e50 100644 --- a/internal/provider/utils/pick.go +++ b/internal/provider/utils/pick.go @@ -26,16 +26,25 @@ func pickConnection(connections []models.Connection, return connection, ErrNoConnectionToPickFrom } - targetIPSet := selection.TargetIP.IsValid() && !selection.TargetIP.IsUnspecified() + var targetIP netip.Addr + switch selection.VPN { + case vpn.OpenVPN: + targetIP = selection.OpenVPN.EndpointIP + case vpn.Wireguard: + targetIP = selection.Wireguard.EndpointIP + default: + panic("unknown VPN type: " + selection.VPN) + } + targetIPSet := targetIP.IsValid() && !targetIP.IsUnspecified() if targetIPSet && selection.VPN == vpn.Wireguard { // we need the right public key - return getTargetIPConnection(connections, selection.TargetIP) + return getTargetIPConnection(connections, targetIP) } connection = pickRandomConnection(connections, randSource) if targetIPSet { - connection.IP = selection.TargetIP + connection.IP = targetIP } return connection, nil diff --git a/internal/storage/formatting.go b/internal/storage/formatting.go index aaebba80..3bf71b93 100644 --- a/internal/storage/formatting.go +++ b/internal/storage/formatting.go @@ -8,6 +8,7 @@ import ( "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/constants/vpn" ) func commaJoin(slice []string) string { @@ -148,9 +149,13 @@ func noServerFoundError(selection settings.ServerSelection) (err error) { messageParts = append(messageParts, "tor only") } - if selection.TargetIP.IsValid() { + targetIP := selection.OpenVPN.EndpointIP + if selection.VPN == vpn.Wireguard { + targetIP = selection.Wireguard.EndpointIP + } + if targetIP.IsValid() { messageParts = append(messageParts, - "target ip address "+selection.TargetIP.String()) + "target ip address "+targetIP.String()) } message := "for " + strings.Join(messageParts, "; ")