diff --git a/Dockerfile b/Dockerfile index 74c44127..023054e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,6 +61,7 @@ ENV VPNSP=pia \ CITY= \ # Mullvad only ISP= \ + OWNED=no \ # Mullvad and Windscribe only PORT= \ # Cyberghost only diff --git a/README.md b/README.md index 0f2b2774..0cf948d3 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo **TLDR**; only set the 🏁 marked environment variables to get started. +💡 For all server filtering options such as `REGION`, you can have multiple values separated by a comma, i.e. `Germany,Singapore` + ### VPN | Variable | Default | Choices | Description | @@ -127,6 +129,7 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo | `CITY` | | One of the [Mullvad cities](https://mullvad.net/en/servers/#openvpn) | VPN server city | | `ISP` | | One of the [Mullvad ISP](https://mullvad.net/en/servers/#openvpn) | VPN server ISP | | `PORT` | | `80`, `443` or `1401` for TCP; `53`, `1194`, `1195`, `1196`, `1197`, `1300`, `1301`, `1302`, `1303` or `1400` for UDP. Defaults to TCP `443` and UDP `1194` | Custom VPN port to use | + | `OWNED` | `no` | `yes` or `no` | If the VPN server is owned by Mullvad | 💡 [Mullvad IPv6 Wiki page](https://github.com/qdm12/gluetun/wiki/Mullvad-IPv6) diff --git a/go.mod b/go.mod index 49c05329..9d02aaf9 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/fatih/color v1.9.0 github.com/golang/mock v1.4.4 github.com/kyokomi/emoji v2.2.4+incompatible - github.com/qdm12/golibs v0.0.0-20201018021451-d64f6f83fb81 + github.com/qdm12/golibs v0.0.0-20201018204514-1d5986880422 github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83 github.com/stretchr/testify v1.6.1 golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 - golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 + golang.org/x/sys v0.0.0-20201018121011-98379d014ca7 ) diff --git a/go.sum b/go.sum index f56c7925..aaf25827 100644 --- a/go.sum +++ b/go.sum @@ -72,10 +72,8 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/qdm12/golibs v0.0.0-20201018021451-d64f6f83fb81 h1:UouAegRn1ZB2BgoA7cA6wE5hr24cN2mgo4Lo8qC9yxo= -github.com/qdm12/golibs v0.0.0-20201018021451-d64f6f83fb81/go.mod h1:xbNrWrKyAZ5akH7lqp/uEA2HTZg+qDOzzugSiyLbzAA= -github.com/qdm12/ss-server v0.0.0-20200819005413-6b516c299307 h1:+LhVxIKpZgUM8ZcopIuc3Yjk+p76dWRdYLQiAA7caZM= -github.com/qdm12/ss-server v0.0.0-20200819005413-6b516c299307/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw= +github.com/qdm12/golibs v0.0.0-20201018204514-1d5986880422 h1:sk+ri/MsgwBqpN3MqOazV8itCid7F2wGS8PbG1kWg9o= +github.com/qdm12/golibs v0.0.0-20201018204514-1d5986880422/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc= github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83 h1:b7sNsgsKxH0mbl9L1hdUp5KSDkZ/1kOQ+iHiBVgFElM= github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -119,11 +117,9 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 h1:XtNJkfEjb4zR3q20BBBcYUykVOEMgZeIUOpBPfNYgxg= -golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018121011-98379d014ca7 h1:CNOpL+H7PSxBI7dF/EIUsfOguRSzWp6CQ91yxZE6PG4= +golang.org/x/sys v0.0.0-20201018121011-98379d014ca7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/models/selection.go b/internal/models/selection.go index 4d664da8..e0a8a07f 100644 --- a/internal/models/selection.go +++ b/internal/models/selection.go @@ -20,24 +20,24 @@ type ServerSelection struct { //nolint:maligned TargetIP net.IP `json:"targetIP,omitempty"` // Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN - Region string `json:"region"` + Regions []string `json:"regions"` // Cyberghost Group string `json:"group"` // Mullvad, PureVPN - Country string `json:"country"` - City string `json:"city"` + Countries []string `json:"countries"` + Cities []string `json:"cities"` // Mullvad - ISP string `json:"isp"` - Owned bool `json:"owned"` + ISPs []string `json:"isps"` + Owned bool `json:"owned"` // Mullvad, Windscribe CustomPort uint16 `json:"customPort"` // NordVPN - Number uint16 `json:"number"` + Numbers []uint16 `json:"numbers"` // PIA EncryptionPreset string `json:"encryptionPreset"` @@ -71,9 +71,9 @@ func (p *ProviderSettings) String() string { if p.ServerSelection.CustomPort > 0 { customPort = fmt.Sprintf("%d", p.ServerSelection.CustomPort) } - number := "" - if p.ServerSelection.Number > 0 { - number = fmt.Sprintf("%d", p.ServerSelection.Number) + numbers := make([]string, len(p.ServerSelection.Numbers)) + for i, number := range p.ServerSelection.Numbers { + numbers[i] = fmt.Sprintf("%d", number) } ipv6 := "off" if p.ExtraConfigOptions.OpenVPNIPv6 { @@ -82,53 +82,53 @@ func (p *ProviderSettings) String() string { switch strings.ToLower(string(p.Name)) { case "private internet access old": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, + "Regions: "+commaJoin(p.ServerSelection.Regions), "Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset, "Port forwarding: "+p.PortForwarding.String(), ) case "private internet access": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, + "Regions: "+commaJoin(p.ServerSelection.Regions), "Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset, "Port forwarding: "+p.PortForwarding.String(), ) case "mullvad": settingsList = append(settingsList, - "Country: "+p.ServerSelection.Country, - "City: "+p.ServerSelection.City, - "ISP: "+p.ServerSelection.ISP, + "Countries: "+commaJoin(p.ServerSelection.Countries), + "Cities: "+commaJoin(p.ServerSelection.Cities), + "ISPs: "+commaJoin(p.ServerSelection.ISPs), "Custom port: "+customPort, "IPv6: "+ipv6, ) case "windscribe": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, + "Regions: "+commaJoin(p.ServerSelection.Regions), "Custom port: "+customPort, ) case "surfshark": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, + "Regions: "+commaJoin(p.ServerSelection.Regions), ) case "cyberghost": settingsList = append(settingsList, "ClientKey: [redacted]", "Group: "+p.ServerSelection.Group, - "Region: "+p.ServerSelection.Region, + "Regions: "+commaJoin(p.ServerSelection.Regions), ) case "vyprvpn": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, + "Regions: "+commaJoin(p.ServerSelection.Regions), ) case "nordvpn": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, - "Number: "+number, + "Regions: "+commaJoin(p.ServerSelection.Regions), + "Numbers: "+commaJoin(numbers), ) case "purevpn": settingsList = append(settingsList, - "Region: "+p.ServerSelection.Region, - "Country: "+p.ServerSelection.Country, - "City: "+p.ServerSelection.City, + "Regions: "+commaJoin(p.ServerSelection.Regions), + "Countries: "+commaJoin(p.ServerSelection.Countries), + "Cities: "+commaJoin(p.ServerSelection.Cities), ) default: settingsList = append(settingsList, @@ -142,3 +142,7 @@ func (p *ProviderSettings) String() string { } return strings.Join(settingsList, "\n |--") } + +func commaJoin(slice []string) string { + return strings.Join(slice, ", ") +} diff --git a/internal/params/cyberghost.go b/internal/params/cyberghost.go index 1e13873f..f5490de4 100644 --- a/internal/params/cyberghost.go +++ b/internal/params/cyberghost.go @@ -14,12 +14,11 @@ func (p *reader) GetCyberghostGroup() (group string, err error) { return s, err } -// GetCyberghostRegion obtains the country name for the Cyberghost server from the +// GetCyberghostRegions obtains the country names for the Cyberghost servers from the // environment variable REGION -func (p *reader) GetCyberghostRegion() (region string, err error) { +func (p *reader) GetCyberghostRegions() (regions []string, err error) { choices := append(constants.CyberghostRegionChoices(), "") - s, err := p.envParams.GetValueIfInside("REGION", choices) - return s, err + return p.envParams.GetCSVInPossibilities("REGION", choices) } // GetCyberghostClientKey obtains the one line client key to use for openvpn from the diff --git a/internal/params/mullvad.go b/internal/params/mullvad.go index 5503a255..bfe51370 100644 --- a/internal/params/mullvad.go +++ b/internal/params/mullvad.go @@ -5,25 +5,25 @@ import ( libparams "github.com/qdm12/golibs/params" ) -// GetMullvadCountry obtains the country for the Mullvad server from the +// GetMullvadCountries obtains the countries for the Mullvad servers from the // environment variable COUNTRY -func (r *reader) GetMullvadCountry() (country string, err error) { +func (r *reader) GetMullvadCountries() (countries []string, err error) { choices := append(constants.MullvadCountryChoices(), "") - return r.envParams.GetValueIfInside("COUNTRY", choices) + return r.envParams.GetCSVInPossibilities("COUNTRY", choices) } -// GetMullvadCity obtains the city for the Mullvad server from the +// GetMullvadCity obtains the cities for the Mullvad servers from the // environment variable CITY -func (r *reader) GetMullvadCity() (country string, err error) { +func (r *reader) GetMullvadCities() (cities []string, err error) { choices := append(constants.MullvadCityChoices(), "") - return r.envParams.GetValueIfInside("CITY", choices) + return r.envParams.GetCSVInPossibilities("CITY", choices) } -// GetMullvadISP obtains the ISP for the Mullvad server from the +// GetMullvadISPs obtains the ISPs for the Mullvad servers from the // environment variable ISP -func (r *reader) GetMullvadISP() (isp string, err error) { +func (r *reader) GetMullvadISPs() (isps []string, err error) { choices := append(constants.MullvadISPChoices(), "") - return r.envParams.GetValueIfInside("ISP", choices) + return r.envParams.GetCSVInPossibilities("ISP", choices) } // GetMullvadPort obtains the port to reach the Mullvad server on from the @@ -32,3 +32,9 @@ func (r *reader) GetMullvadPort() (port uint16, err error) { n, err := r.envParams.GetEnvIntRange("PORT", 0, 65535, libparams.Default("0")) return uint16(n), err } + +// GetMullvadOwned obtains if the server should be owned by Mullvad or not from the +// environment variable OWNED +func (r *reader) GetMullvadOwned() (owned bool, err error) { + return r.envParams.GetYesNo("OWNED", libparams.Default("no")) +} diff --git a/internal/params/nordvpn.go b/internal/params/nordvpn.go index 1a0eee27..27d162da 100644 --- a/internal/params/nordvpn.go +++ b/internal/params/nordvpn.go @@ -1,23 +1,37 @@ package params import ( + "fmt" + "strconv" + "github.com/qdm12/gluetun/internal/constants" - libparams "github.com/qdm12/golibs/params" ) -// GetNordvpnRegion obtains the region (country) for the NordVPN server from the +// GetNordvpnRegions obtains the regions (countries) for the NordVPN server from the // environment variable REGION -func (r *reader) GetNordvpnRegion() (region string, err error) { +func (r *reader) GetNordvpnRegions() (regions []string, err error) { choices := append(constants.NordvpnRegionChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } -// GetNordvpnRegion obtains the server number (optional) for the NordVPN server from the +// GetNordvpnRegion obtains the server numbers (optional) for the NordVPN servers from the // environment variable SERVER_NUMBER -func (r *reader) GetNordvpnNumber() (number uint16, err error) { - n, err := r.envParams.GetEnvIntRange("SERVER_NUMBER", 0, 65535, libparams.Default("0")) - if err != nil { - return 0, err +func (r *reader) GetNordvpnNumbers() (numbers []uint16, err error) { + possibilities := make([]string, 65536) + for i := range possibilities { + possibilities[i] = fmt.Sprintf("%d", i) } - return uint16(n), nil + values, err := r.envParams.GetCSVInPossibilities("SERVER_NUMBER", possibilities) + if err != nil { + return nil, err + } + numbers = make([]uint16, len(values)) + for i := range values { + n, err := strconv.Atoi(values[i]) + if err != nil { + return nil, err + } + numbers[i] = uint16(n) + } + return numbers, nil } diff --git a/internal/params/params.go b/internal/params/params.go index 4fbccf7c..96ab4b72 100644 --- a/internal/params/params.go +++ b/internal/params/params.go @@ -61,38 +61,39 @@ type Reader interface { GetPortForwarding() (activated bool, err error) GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) GetPIAEncryptionPreset() (preset string, err error) - GetPIARegion() (region string, err error) - GetPIAOldRegion() (region string, err error) + GetPIARegions() (regions []string, err error) + GetPIAOldRegions() (regions []string, err error) // Mullvad getters - GetMullvadCountry() (country string, err error) - GetMullvadCity() (country string, err error) - GetMullvadISP() (country string, err error) + GetMullvadCountries() (countries []string, err error) + GetMullvadCities() (cities []string, err error) + GetMullvadISPs() (ips []string, err error) GetMullvadPort() (port uint16, err error) + GetMullvadOwned() (owned bool, err error) // Windscribe getters - GetWindscribeRegion() (country string, err error) + GetWindscribeRegions() (countries []string, err error) GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) // Surfshark getters - GetSurfsharkRegion() (country string, err error) + GetSurfsharkRegions() (countries []string, err error) // Cyberghost getters GetCyberghostGroup() (group string, err error) - GetCyberghostRegion() (region string, err error) + GetCyberghostRegions() (regions []string, err error) GetCyberghostClientKey() (clientKey string, err error) // Vyprvpn getters - GetVyprvpnRegion() (region string, err error) + GetVyprvpnRegions() (regions []string, err error) // NordVPN getters - GetNordvpnRegion() (region string, err error) - GetNordvpnNumber() (number uint16, err error) + GetNordvpnRegions() (regions []string, err error) + GetNordvpnNumbers() (numbers []uint16, err error) // PureVPN getters - GetPurevpnRegion() (region string, err error) - GetPurevpnCountry() (country string, err error) - GetPurevpnCity() (city string, err error) + GetPurevpnRegions() (regions []string, err error) + GetPurevpnCountries() (countries []string, err error) + GetPurevpnCities() (cities []string, err error) // Shadowsocks getters GetShadowSocks() (activated bool, err error) diff --git a/internal/params/pia.go b/internal/params/pia.go index 105409e5..05c29f5e 100644 --- a/internal/params/pia.go +++ b/internal/params/pia.go @@ -56,16 +56,16 @@ func (r *reader) GetPIAEncryptionPreset() (preset string, err error) { libparams.Default(constants.PIAEncryptionPresetStrong)) } -// GetPIARegion obtains the region for the PIA server from the +// GetPIARegions obtains the regions for the PIA servers from the // environment variable REGION -func (r *reader) GetPIARegion() (region string, err error) { +func (r *reader) GetPIARegions() (regions []string, err error) { choices := append(constants.PIAGeoChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } -// GetPIAOldRegion obtains the region for the PIA server from the +// GetPIAOldRegions obtains the regions for the PIA servers from the // environment variable REGION -func (r *reader) GetPIAOldRegion() (region string, err error) { +func (r *reader) GetPIAOldRegions() (regions []string, err error) { choices := append(constants.PIAOldGeoChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } diff --git a/internal/params/purevpn.go b/internal/params/purevpn.go index ce40f9b8..440dc995 100644 --- a/internal/params/purevpn.go +++ b/internal/params/purevpn.go @@ -4,23 +4,23 @@ import ( "github.com/qdm12/gluetun/internal/constants" ) -// GetPurevpnRegion obtains the region (continent) for the PureVPN server from the +// GetPurevpnRegions obtains the regions (continents) for the PureVPN servers from the // environment variable REGION -func (r *reader) GetPurevpnRegion() (region string, err error) { +func (r *reader) GetPurevpnRegions() (regions []string, err error) { choices := append(constants.PurevpnRegionChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } -// GetPurevpnCountry obtains the country for the PureVPN server from the +// GetPurevpnCountries obtains the countries for the PureVPN servers from the // environment variable COUNTRY -func (r *reader) GetPurevpnCountry() (country string, err error) { +func (r *reader) GetPurevpnCountries() (countries []string, err error) { choices := append(constants.PurevpnCountryChoices(), "") - return r.envParams.GetValueIfInside("COUNTRY", choices) + return r.envParams.GetCSVInPossibilities("COUNTRY", choices) } -// GetPurevpnCity obtains the city for the PureVPN server from the +// GetPurevpnCities obtains the cities for the PureVPN servers from the // environment variable CITY -func (r *reader) GetPurevpnCity() (city string, err error) { +func (r *reader) GetPurevpnCities() (cities []string, err error) { choices := append(constants.PurevpnCityChoices(), "") - return r.envParams.GetValueIfInside("CITY", choices) + return r.envParams.GetCSVInPossibilities("CITY", choices) } diff --git a/internal/params/surfshark.go b/internal/params/surfshark.go index 1669c827..82084d5b 100644 --- a/internal/params/surfshark.go +++ b/internal/params/surfshark.go @@ -4,9 +4,9 @@ import ( "github.com/qdm12/gluetun/internal/constants" ) -// GetSurfsharkRegion obtains the region for the Surfshark server from the +// GetSurfsharkRegions obtains the regions for the Surfshark servers from the // environment variable REGION -func (r *reader) GetSurfsharkRegion() (region string, err error) { +func (r *reader) GetSurfsharkRegions() (regions []string, err error) { choices := append(constants.SurfsharkRegionChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } diff --git a/internal/params/vypervpn.go b/internal/params/vypervpn.go index 76775a30..948f6d04 100644 --- a/internal/params/vypervpn.go +++ b/internal/params/vypervpn.go @@ -4,9 +4,9 @@ import ( "github.com/qdm12/gluetun/internal/constants" ) -// GetVyprvpnRegion obtains the region for the Vyprvpn server from the +// GetVyprvpnRegions obtains the regions for the Vyprvpn servers from the // environment variable REGION -func (r *reader) GetVyprvpnRegion() (region string, err error) { +func (r *reader) GetVyprvpnRegions() (regions []string, err error) { choices := append(constants.VyprvpnRegionChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } diff --git a/internal/params/windscribe.go b/internal/params/windscribe.go index 7dcda745..87c4e1be 100644 --- a/internal/params/windscribe.go +++ b/internal/params/windscribe.go @@ -8,11 +8,11 @@ import ( libparams "github.com/qdm12/golibs/params" ) -// GetWindscribeRegion obtains the region for the Windscribe server from the +// GetWindscribeRegions obtains the regions for the Windscribe servers from the // environment variable REGION -func (r *reader) GetWindscribeRegion() (region string, err error) { +func (r *reader) GetWindscribeRegions() (regions []string, err error) { choices := append(constants.WindscribeRegionChoices(), "") - return r.envParams.GetValueIfInside("REGION", choices) + return r.envParams.GetCSVInPossibilities("REGION", choices) } // GetMullvadPort obtains the port to reach the Mullvad server on from the diff --git a/internal/provider/cyberghost.go b/internal/provider/cyberghost.go index bbd3dd52..aecc95ea 100644 --- a/internal/provider/cyberghost.go +++ b/internal/provider/cyberghost.go @@ -27,16 +27,13 @@ func newCyberghost(servers []models.CyberghostServer, timeNow timeNowFunc) *cybe } } -func (c *cyberghost) filterServers(region, group string) (servers []models.CyberghostServer) { - for i, server := range c.servers { - if len(region) == 0 { - server.Region = "" - } - if len(group) == 0 { - server.Group = "" - } - if strings.EqualFold(server.Region, region) && strings.EqualFold(server.Group, group) { - servers = append(servers, c.servers[i]) +func (c *cyberghost) filterServers(regions []string, group string) (servers []models.CyberghostServer) { + for _, server := range c.servers { + switch { + case len(group) > 0 && group != server.Group, + filterByPossibilities(server.Region, regions): + default: + servers = append(servers, server) } } return servers @@ -47,9 +44,9 @@ func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) (con return models.OpenVPNConnection{IP: selection.TargetIP, Port: 443, Protocol: selection.Protocol}, nil } - servers := c.filterServers(selection.Region, selection.Group) + servers := c.filterServers(selection.Regions, selection.Group) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q and group %q", selection.Region, selection.Group) + return connection, fmt.Errorf("no server found for regions %s and group %q", commaJoin(selection.Regions), selection.Group) } var connections []models.OpenVPNConnection diff --git a/internal/provider/mullvad.go b/internal/provider/mullvad.go index c23ed7cf..06d0dc43 100644 --- a/internal/provider/mullvad.go +++ b/internal/provider/mullvad.go @@ -6,7 +6,6 @@ import ( "math/rand" "net" "net/http" - "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" @@ -27,21 +26,16 @@ func newMullvad(servers []models.MullvadServer, timeNow timeNowFunc) *mullvad { } } -func (m *mullvad) filterServers(country, city, isp string) (servers []models.MullvadServer) { - for i, server := range m.servers { - if len(country) == 0 { - server.Country = "" - } - if len(city) == 0 { - server.City = "" - } - if len(isp) == 0 { - server.ISP = "" - } - if strings.EqualFold(server.Country, country) && - strings.EqualFold(server.City, city) && - strings.EqualFold(server.ISP, isp) { - servers = append(servers, m.servers[i]) +func (m *mullvad) filterServers(countries, cities, isps []string, owned bool) (servers []models.MullvadServer) { + for _, server := range m.servers { + switch { + case + filterByPossibilities(server.Country, countries), + filterByPossibilities(server.City, cities), + filterByPossibilities(server.ISP, isps), + owned && !server.Owned: + default: + servers = append(servers, server) } } return servers @@ -61,9 +55,10 @@ func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) (connec return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := m.filterServers(selection.Country, selection.City, selection.ISP) + servers := m.filterServers(selection.Countries, selection.Cities, selection.ISPs, selection.Owned) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for country %q, city %q and ISP %q", selection.Country, selection.City, selection.ISP) + return connection, fmt.Errorf("no server found for countries %s, cities %s, ISPs %s and owned %t", + commaJoin(selection.Countries), commaJoin(selection.Cities), commaJoin(selection.ISPs), selection.Owned) } var connections []models.OpenVPNConnection diff --git a/internal/provider/nordvpn.go b/internal/provider/nordvpn.go index 332f9ad2..f0322d8d 100644 --- a/internal/provider/nordvpn.go +++ b/internal/provider/nordvpn.go @@ -6,7 +6,6 @@ import ( "math/rand" "net" "net/http" - "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" @@ -27,22 +26,21 @@ func newNordvpn(servers []models.NordvpnServer, timeNow timeNowFunc) *nordvpn { } } -func (n *nordvpn) filterServers(region string, protocol models.NetworkProtocol, number uint16) (servers []models.NordvpnServer) { - for i, server := range n.servers { - if len(region) == 0 { - server.Region = "" - } - if number == 0 { - server.Number = 0 - } - - if protocol == constants.TCP && !server.TCP { - continue - } else if protocol == constants.UDP && !server.UDP { - continue - } - if strings.EqualFold(server.Region, region) && server.Number == number { - servers = append(servers, n.servers[i]) +func (n *nordvpn) filterServers(regions []string, protocol models.NetworkProtocol, numbers []uint16) (servers []models.NordvpnServer) { + numbersStr := make([]string, len(numbers)) + for i := range numbers { + numbersStr[i] = fmt.Sprintf("%d", numbers[i]) + } + for _, server := range n.servers { + numberStr := fmt.Sprintf("%d", server.Number) + switch { + case + protocol == constants.TCP && !server.TCP, + protocol == constants.UDP && !server.UDP, + filterByPossibilities(server.Region, regions), + filterByPossibilities(numberStr, numbersStr): + default: + servers = append(servers, server) } } return servers @@ -63,9 +61,9 @@ func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) (connec return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := n.filterServers(selection.Region, selection.Protocol, selection.Number) + servers := n.filterServers(selection.Regions, selection.Protocol, selection.Numbers) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q, protocol %s and number %d", selection.Region, selection.Protocol, selection.Number) + return connection, fmt.Errorf("no server found for region %s, protocol %s and numbers %v", commaJoin(selection.Regions), selection.Protocol, selection.Numbers) } connections := make([]models.OpenVPNConnection, len(servers)) diff --git a/internal/provider/piav3.go b/internal/provider/piav3.go index 8d286b48..6781de44 100644 --- a/internal/provider/piav3.go +++ b/internal/provider/piav3.go @@ -9,7 +9,6 @@ import ( "math/rand" "net" "net/http" - "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" @@ -57,9 +56,9 @@ func (p *piaV3) GetOpenVPNConnection(selection models.ServerSelection) (connecti return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := filterPIAOldServers(p.servers, selection.Region) + servers := filterPIAOldServers(p.servers, selection.Regions) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q", selection.Region) + return connection, fmt.Errorf("no server found for regions %s", commaJoin(selection.Regions)) } var connections []models.OpenVPNConnection @@ -136,14 +135,13 @@ func (p *piaV3) PortForward(ctx context.Context, client *http.Client, } } -func filterPIAOldServers(servers []models.PIAOldServer, region string) (filtered []models.PIAOldServer) { - if len(region) == 0 { - return servers - } +func filterPIAOldServers(servers []models.PIAOldServer, regions []string) (filtered []models.PIAOldServer) { for _, server := range servers { - if strings.EqualFold(server.Region, region) { - return []models.PIAOldServer{server} + switch { + case filterByPossibilities(server.Region, regions): + default: + servers = append(servers, server) } } - return nil + return servers } diff --git a/internal/provider/piav4.go b/internal/provider/piav4.go index 60662c84..b2ab694a 100644 --- a/internal/provider/piav4.go +++ b/internal/provider/piav4.go @@ -66,9 +66,9 @@ func (p *piaV4) GetOpenVPNConnection(selection models.ServerSelection) (connecti return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := filterPIAServers(p.servers, selection.Region) + servers := filterPIAServers(p.servers, selection.Regions) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q", selection.Region) + return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions)) } var connections []models.OpenVPNConnection @@ -246,16 +246,15 @@ func (p *piaV4) PortForward(ctx context.Context, client *http.Client, } } -func filterPIAServers(servers []models.PIAServer, region string) (filtered []models.PIAServer) { - if len(region) == 0 { - return servers - } +func filterPIAServers(servers []models.PIAServer, regions []string) (filtered []models.PIAServer) { for _, server := range servers { - if strings.EqualFold(server.Region, region) { - return []models.PIAServer{server} + switch { + case filterByPossibilities(server.Region, regions): + default: + servers = append(servers, server) } } - return nil + return servers } func newPIAv4HTTPClient(serverName string) (client *http.Client, err error) { diff --git a/internal/provider/purevpn.go b/internal/provider/purevpn.go index 187cae2c..9b594c9c 100644 --- a/internal/provider/purevpn.go +++ b/internal/provider/purevpn.go @@ -6,7 +6,6 @@ import ( "math/rand" "net" "net/http" - "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" @@ -27,21 +26,15 @@ func newPurevpn(servers []models.PurevpnServer, timeNow timeNowFunc) *purevpn { } } -func (p *purevpn) filterServers(region, country, city string) (servers []models.PurevpnServer) { - for i, server := range p.servers { - if len(region) == 0 { - server.Region = "" - } - if len(country) == 0 { - server.Country = "" - } - if len(city) == 0 { - server.City = "" - } - if strings.EqualFold(server.Region, region) && - strings.EqualFold(server.Country, country) && - strings.EqualFold(server.City, city) { - servers = append(servers, p.servers[i]) +func (p *purevpn) filterServers(regions, countries, cities []string) (servers []models.PurevpnServer) { + for _, server := range p.servers { + switch { + case + filterByPossibilities(server.Region, regions), + filterByPossibilities(server.Country, countries), + filterByPossibilities(server.City, cities): + default: + servers = append(servers, server) } } return servers @@ -62,9 +55,10 @@ func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) (connec return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := p.filterServers(selection.Region, selection.Country, selection.City) + servers := p.filterServers(selection.Regions, selection.Countries, selection.Cities) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q, country %q and city %q", selection.Region, selection.Country, selection.City) + return connection, fmt.Errorf("no server found for regions %s, countries %s and cities %s", + commaJoin(selection.Regions), commaJoin(selection.Countries), commaJoin(selection.Cities)) } var connections []models.OpenVPNConnection diff --git a/internal/provider/surfshark.go b/internal/provider/surfshark.go index 520aa8a4..df0f8a02 100644 --- a/internal/provider/surfshark.go +++ b/internal/provider/surfshark.go @@ -6,7 +6,6 @@ import ( "math/rand" "net" "net/http" - "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" @@ -27,16 +26,16 @@ func newSurfshark(servers []models.SurfsharkServer, timeNow timeNowFunc) *surfsh } } -func (s *surfshark) filterServers(region string) (servers []models.SurfsharkServer) { - if len(region) == 0 { - return s.servers - } +func (s *surfshark) filterServers(regions []string) (servers []models.SurfsharkServer) { for _, server := range s.servers { - if strings.EqualFold(server.Region, region) { - return []models.SurfsharkServer{server} + switch { + case + filterByPossibilities(server.Region, regions): + default: + servers = append(servers, server) } } - return nil + return servers } func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { //nolint:dupl @@ -54,9 +53,9 @@ func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) (conn return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := s.filterServers(selection.Region) + servers := s.filterServers(selection.Regions) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q", selection.Region) + return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions)) } var connections []models.OpenVPNConnection diff --git a/internal/provider/utils.go b/internal/provider/utils.go index 67a06447..fb9b312a 100644 --- a/internal/provider/utils.go +++ b/internal/provider/utils.go @@ -3,6 +3,7 @@ package provider import ( "context" "math/rand" + "strings" "time" "github.com/qdm12/gluetun/internal/models" @@ -35,3 +36,19 @@ func tryUntilSuccessful(ctx context.Context, logger logging.Logger, fn func() er func pickRandomConnection(connections []models.OpenVPNConnection, source rand.Source) models.OpenVPNConnection { return connections[rand.New(source).Intn(len(connections))] //nolint:gosec } + +func filterByPossibilities(value string, possibilities []string) (filtered bool) { + if len(possibilities) == 0 { + return false + } + for _, possibility := range possibilities { + if strings.EqualFold(value, possibility) { + return false + } + } + return true +} + +func commaJoin(slice []string) string { + return strings.Join(slice, ",") +} diff --git a/internal/provider/vyprvpn.go b/internal/provider/vyprvpn.go index d063d170..c389024d 100644 --- a/internal/provider/vyprvpn.go +++ b/internal/provider/vyprvpn.go @@ -6,7 +6,6 @@ import ( "math/rand" "net" "net/http" - "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" @@ -27,16 +26,16 @@ func newVyprvpn(servers []models.VyprvpnServer, timeNow timeNowFunc) *vyprvpn { } } -func (v *vyprvpn) filterServers(region string) (servers []models.VyprvpnServer) { - if len(region) == 0 { - return v.servers - } +func (v *vyprvpn) filterServers(regions []string) (servers []models.VyprvpnServer) { for _, server := range v.servers { - if strings.EqualFold(server.Region, region) { - return []models.VyprvpnServer{server} + switch { + case + filterByPossibilities(server.Region, regions): + default: + servers = append(servers, server) } } - return nil + return servers } func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { @@ -54,9 +53,9 @@ func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) (connec return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := v.filterServers(selection.Region) + servers := v.filterServers(selection.Regions) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q", selection.Region) + return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions)) } var connections []models.OpenVPNConnection diff --git a/internal/provider/windscribe.go b/internal/provider/windscribe.go index f366b0cf..cbb3fce7 100644 --- a/internal/provider/windscribe.go +++ b/internal/provider/windscribe.go @@ -27,16 +27,16 @@ func newWindscribe(servers []models.WindscribeServer, timeNow timeNowFunc) *wind } } -func (w *windscribe) filterServers(region string) (servers []models.WindscribeServer) { - if len(region) == 0 { - return w.servers - } +func (w *windscribe) filterServers(regions []string) (servers []models.WindscribeServer) { for _, server := range w.servers { - if strings.EqualFold(server.Region, region) { - return []models.WindscribeServer{server} + switch { + case + filterByPossibilities(server.Region, regions): + default: + servers = append(servers, server) } } - return nil + return servers } func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { @@ -56,9 +56,9 @@ func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (con return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil } - servers := w.filterServers(selection.Region) + servers := w.filterServers(selection.Regions) if len(servers) == 0 { - return connection, fmt.Errorf("no server found for region %q", selection.Region) + return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions)) } var connections []models.OpenVPNConnection diff --git a/internal/settings/openvpn_test.go b/internal/settings/openvpn_test.go index 3339f822..dc3a8f89 100644 --- a/internal/settings/openvpn_test.go +++ b/internal/settings/openvpn_test.go @@ -19,7 +19,7 @@ func Test_OpenVPN_JSON(t *testing.T) { } data, err := json.Marshal(in) require.NoError(t, err) - assert.Equal(t, `{"user":"","verbosity":0,"runAsRoot":true,"cipher":"","auth":"","provider":{"name":"name","serverSelection":{"networkProtocol":"","region":"","group":"","country":"","city":"","isp":"","owned":false,"customPort":0,"number":0,"encryptionPreset":""},"extraConfig":{"encryptionPreset":"","openvpnIPv6":false},"portForwarding":{"enabled":false,"filepath":""}}}`, string(data)) + assert.Equal(t, `{"user":"","verbosity":0,"runAsRoot":true,"cipher":"","auth":"","provider":{"name":"name","serverSelection":{"networkProtocol":"","regions":null,"group":"","countries":null,"cities":null,"isps":null,"owned":false,"customPort":0,"numbers":null,"encryptionPreset":""},"extraConfig":{"encryptionPreset":"","openvpnIPv6":false},"portForwarding":{"enabled":false,"filepath":""}}}`, string(data)) var out OpenVPN err = json.Unmarshal(data, &out) require.NoError(t, err) diff --git a/internal/settings/providers.go b/internal/settings/providers.go index 2299d155..ec0cf91e 100644 --- a/internal/settings/providers.go +++ b/internal/settings/providers.go @@ -34,7 +34,7 @@ func getPIASettings(paramsReader params.Reader, name models.VPNProvider) (settin } settings.ServerSelection.EncryptionPreset = encryptionPreset settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset - settings.ServerSelection.Region, err = paramsReader.GetPIARegion() + settings.ServerSelection.Regions, err = paramsReader.GetPIARegions() if err != nil { return settings, err } @@ -62,15 +62,15 @@ func GetMullvadSettings(paramsReader params.Reader) (settings models.ProviderSet if err != nil { return settings, err } - settings.ServerSelection.Country, err = paramsReader.GetMullvadCountry() + settings.ServerSelection.Countries, err = paramsReader.GetMullvadCountries() if err != nil { return settings, err } - settings.ServerSelection.City, err = paramsReader.GetMullvadCity() + settings.ServerSelection.Cities, err = paramsReader.GetMullvadCities() if err != nil { return settings, err } - settings.ServerSelection.ISP, err = paramsReader.GetMullvadISP() + settings.ServerSelection.ISPs, err = paramsReader.GetMullvadISPs() if err != nil { return settings, err } @@ -91,6 +91,10 @@ func GetMullvadSettings(paramsReader params.Reader) (settings models.ProviderSet return settings, fmt.Errorf("port %d is not valid for UDP protocol", settings.ServerSelection.CustomPort) } } + settings.ServerSelection.Owned, err = paramsReader.GetMullvadOwned() + if err != nil { + return settings, err + } settings.ExtraConfigOptions.OpenVPNIPv6, err = paramsReader.GetOpenVPNIPv6() if err != nil { return settings, err @@ -109,7 +113,7 @@ func GetWindscribeSettings(paramsReader params.Reader) (settings models.Provider if err != nil { return settings, err } - settings.ServerSelection.Region, err = paramsReader.GetWindscribeRegion() + settings.ServerSelection.Regions, err = paramsReader.GetWindscribeRegions() if err != nil { return settings, err } @@ -131,7 +135,7 @@ func GetSurfsharkSettings(paramsReader params.Reader) (settings models.ProviderS if err != nil { return settings, err } - settings.ServerSelection.Region, err = paramsReader.GetSurfsharkRegion() + settings.ServerSelection.Regions, err = paramsReader.GetSurfsharkRegions() if err != nil { return settings, err } @@ -157,7 +161,7 @@ func GetCyberghostSettings(paramsReader params.Reader) (settings models.Provider if err != nil { return settings, err } - settings.ServerSelection.Region, err = paramsReader.GetCyberghostRegion() + settings.ServerSelection.Regions, err = paramsReader.GetCyberghostRegions() if err != nil { return settings, err } @@ -175,7 +179,7 @@ func GetVyprvpnSettings(paramsReader params.Reader) (settings models.ProviderSet if err != nil { return settings, err } - settings.ServerSelection.Region, err = paramsReader.GetVyprvpnRegion() + settings.ServerSelection.Regions, err = paramsReader.GetVyprvpnRegions() if err != nil { return settings, err } @@ -193,11 +197,11 @@ func GetNordvpnSettings(paramsReader params.Reader) (settings models.ProviderSet if err != nil { return settings, err } - settings.ServerSelection.Region, err = paramsReader.GetNordvpnRegion() + settings.ServerSelection.Regions, err = paramsReader.GetNordvpnRegions() if err != nil { return settings, err } - settings.ServerSelection.Number, err = paramsReader.GetNordvpnNumber() + settings.ServerSelection.Numbers, err = paramsReader.GetNordvpnNumbers() if err != nil { return settings, err } @@ -215,15 +219,15 @@ func GetPurevpnSettings(paramsReader params.Reader) (settings models.ProviderSet if err != nil { return settings, err } - settings.ServerSelection.Region, err = paramsReader.GetPurevpnRegion() + settings.ServerSelection.Regions, err = paramsReader.GetPurevpnRegions() if err != nil { return settings, err } - settings.ServerSelection.Country, err = paramsReader.GetPurevpnCountry() + settings.ServerSelection.Countries, err = paramsReader.GetPurevpnCountries() if err != nil { return settings, err } - settings.ServerSelection.City, err = paramsReader.GetPurevpnCity() + settings.ServerSelection.Cities, err = paramsReader.GetPurevpnCities() if err != nil { return settings, err }