Fix & feat: Cyberghost server groups

- Allow multiple comma separated values for CYBERGHOST_GROUP
- Defaults to all UDP groups
- If TCP is enabled, defaults to all TCP groups
- Check groups specified match the protocol
- Default Cyberghost group to empty
- Adjust formatting and messages
This commit is contained in:
Quentin McGaw (desktop)
2021-07-31 14:53:34 +00:00
parent c17b351efb
commit 982536e9e8
9 changed files with 199 additions and 48 deletions

View File

@@ -4,11 +4,10 @@ import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
) )
func (settings *Provider) cyberghostLines() (lines []string) { func (settings *Provider) cyberghostLines() (lines []string) {
lines = append(lines, lastIndent+"Server group: "+settings.ServerSelection.Group) lines = append(lines, lastIndent+"Server groups: "+commaJoin(settings.ServerSelection.Groups))
if len(settings.ServerSelection.Regions) > 0 { if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
@@ -52,8 +51,8 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
return err return err
} }
settings.ServerSelection.Group, err = r.env.Inside("CYBERGHOST_GROUP", settings.ServerSelection.Groups, err = r.env.CSVInside("CYBERGHOST_GROUP",
constants.CyberghostGroupChoices(), params.Default("Premium UDP Europe")) constants.CyberghostGroupChoices())
if err != nil { if err != nil {
return fmt.Errorf("environment variable CYBERGHOST_GROUP: %w", err) return fmt.Errorf("environment variable CYBERGHOST_GROUP: %w", err)
} }

View File

@@ -33,7 +33,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
"server_selection": { "server_selection": {
"tcp": false, "tcp": false,
"regions": null, "regions": null,
"group": "", "groups": null,
"countries": null, "countries": null,
"cities": null, "cities": null,
"hostnames": null, "hostnames": null,

View File

@@ -24,7 +24,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{ settings: Provider{
Name: constants.Cyberghost, Name: constants.Cyberghost,
ServerSelection: ServerSelection{ ServerSelection: ServerSelection{
Group: "group", Groups: []string{"group"},
Regions: []string{"a", "El country"}, Regions: []string{"a", "El country"},
}, },
ExtraConfigOptions: ExtraConfigOptions{ ExtraConfigOptions: ExtraConfigOptions{
@@ -35,7 +35,7 @@ func Test_Provider_lines(t *testing.T) {
lines: []string{ lines: []string{
"|--Cyberghost settings:", "|--Cyberghost settings:",
" |--Network protocol: udp", " |--Network protocol: udp",
" |--Server group: group", " |--Server groups: group",
" |--Regions: a, El country", " |--Regions: a, El country",
" |--Client key is set", " |--Client key is set",
" |--Client certificate is set", " |--Client certificate is set",

View File

@@ -13,7 +13,7 @@ type ServerSelection struct { //nolint:maligned
Regions []string `json:"regions"` Regions []string `json:"regions"`
// Cyberghost // Cyberghost
Group string `json:"group"` Groups []string `json:"groups"`
// Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited // Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
Countries []string `json:"countries"` Countries []string `json:"countries"`

View File

@@ -1,6 +1,8 @@
package constants package constants
import ( import (
"sort"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
) )
@@ -20,11 +22,20 @@ func CyberghostRegionChoices() (choices []string) {
func CyberghostGroupChoices() (choices []string) { func CyberghostGroupChoices() (choices []string) {
servers := CyberghostServers() servers := CyberghostServers()
choices = make([]string, len(servers)) uniqueChoices := map[string]struct{}{}
for i := range servers { for _, server := range servers {
choices[i] = servers[i].Group uniqueChoices[server.Group] = struct{}{}
} }
return makeUnique(choices)
choices = make([]string, 0, len(uniqueChoices))
for choice := range uniqueChoices {
choices = append(choices, choice)
}
sortable := sort.StringSlice(choices)
sortable.Sort()
return sortable
} }
func CyberghostHostnameChoices() (choices []string) { func CyberghostHostnameChoices() (choices []string) {

View File

@@ -0,0 +1,18 @@
package constants
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_CyberghostGroupChoices(t *testing.T) {
t.Parallel()
expected := []string{"Premium TCP Asia", "Premium TCP Europe",
"Premium TCP USA", "Premium UDP Asia", "Premium UDP Europe",
"Premium UDP USA"}
choices := CyberghostGroupChoices()
assert.Equal(t, expected, choices)
}

View File

@@ -1,18 +1,41 @@
package cyberghost package cyberghost
import ( import (
"errors"
"fmt"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils" "github.com/qdm12/gluetun/internal/provider/utils"
) )
var ErrGroupMismatchesProtocol = errors.New("server group does not match protocol")
func (c *Cyberghost) filterServers(selection configuration.ServerSelection) ( func (c *Cyberghost) filterServers(selection configuration.ServerSelection) (
servers []models.CyberghostServer, err error) { servers []models.CyberghostServer, err error) {
if len(selection.Groups) == 0 {
if selection.TCP {
selection.Groups = tcpGroupChoices()
} else {
selection.Groups = udpGroupChoices()
}
}
// Check each group match the protocol
groupsCheckFn := groupsAreAllUDP
if selection.TCP {
groupsCheckFn = groupsAreAllTCP
}
if err := groupsCheckFn(selection.Groups); err != nil {
return nil, err
}
for _, server := range c.servers { for _, server := range c.servers {
switch { switch {
case selection.Group != "" && !strings.EqualFold(selection.Group, server.Group), // TODO make CSV case
utils.FilterByPossibilities(server.Group, selection.Groups),
utils.FilterByPossibilities(server.Region, selection.Regions), utils.FilterByPossibilities(server.Region, selection.Regions),
utils.FilterByPossibilities(server.Hostname, selection.Hostnames): utils.FilterByPossibilities(server.Hostname, selection.Hostnames):
default: default:
@@ -26,3 +49,51 @@ func (c *Cyberghost) filterServers(selection configuration.ServerSelection) (
return servers, nil return servers, nil
} }
func tcpGroupChoices() (choices []string) {
const tcp = true
return groupsForTCP(tcp)
}
func udpGroupChoices() (choices []string) {
const tcp = false
return groupsForTCP(tcp)
}
func groupsForTCP(tcp bool) (choices []string) {
allGroups := constants.CyberghostGroupChoices()
choices = make([]string, 0, len(allGroups))
for _, group := range allGroups {
switch {
case tcp && groupIsTCP(group):
choices = append(choices, group)
case !tcp && !groupIsTCP(group):
choices = append(choices, group)
}
}
return choices
}
func groupIsTCP(group string) bool {
return strings.Contains(strings.ToLower(group), "tcp")
}
func groupsAreAllTCP(groups []string) error {
for _, group := range groups {
if !groupIsTCP(group) {
return fmt.Errorf("%w: group %s for protocol TCP",
ErrGroupMismatchesProtocol, group)
}
}
return nil
}
func groupsAreAllUDP(groups []string) error {
for _, group := range groups {
if groupIsTCP(group) {
return fmt.Errorf("%w: group %s for protocol UDP",
ErrGroupMismatchesProtocol, group)
}
}
return nil
}

View File

@@ -21,77 +21,102 @@ func Test_Cyberghost_filterServers(t *testing.T) {
"no servers": { "no servers": {
err: errors.New("no server found: for protocol udp"), err: errors.New("no server found: for protocol udp"),
}, },
"servers without filter": { "servers without filter defaults to UDP": {
servers: []models.CyberghostServer{ servers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium TCP Asia"},
{Region: "b", Group: "1"}, {Region: "b", Group: "Premium TCP Europe"},
{Region: "c", Group: "2"}, {Region: "c", Group: "Premium UDP Asia"},
{Region: "d", Group: "2"}, {Region: "d", Group: "Premium UDP Europe"},
}, },
filteredServers: []models.CyberghostServer{ filteredServers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "c", Group: "Premium UDP Asia"},
{Region: "b", Group: "1"}, {Region: "d", Group: "Premium UDP Europe"},
{Region: "c", Group: "2"}, },
{Region: "d", Group: "2"}, },
"servers with TCP selection": {
servers: []models.CyberghostServer{
{Region: "a", Group: "Premium TCP Asia"},
{Region: "b", Group: "Premium TCP Europe"},
{Region: "c", Group: "Premium UDP Asia"},
{Region: "d", Group: "Premium UDP Europe"},
},
selection: configuration.ServerSelection{
TCP: true,
},
filteredServers: []models.CyberghostServer{
{Region: "a", Group: "Premium TCP Asia"},
{Region: "b", Group: "Premium TCP Europe"},
}, },
}, },
"servers with regions filter": { "servers with regions filter": {
servers: []models.CyberghostServer{ servers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium UDP Asia"},
{Region: "b", Group: "1"}, {Region: "b", Group: "Premium UDP Asia"},
{Region: "c", Group: "2"}, {Region: "c", Group: "Premium UDP Asia"},
{Region: "d", Group: "2"}, {Region: "d", Group: "Premium UDP Asia"},
}, },
selection: configuration.ServerSelection{ selection: configuration.ServerSelection{
Regions: []string{"a", "c"}, Regions: []string{"a", "c"},
}, },
filteredServers: []models.CyberghostServer{ filteredServers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium UDP Asia"},
{Region: "c", Group: "2"}, {Region: "c", Group: "Premium UDP Asia"},
}, },
}, },
"servers with group filter": { "servers with group filter": {
servers: []models.CyberghostServer{ servers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium UDP Europe"},
{Region: "b", Group: "1"}, {Region: "b", Group: "Premium UDP Europe"},
{Region: "c", Group: "2"}, {Region: "c", Group: "Premium TCP Europe"},
{Region: "d", Group: "2"}, {Region: "d", Group: "Premium TCP Europe"},
}, },
selection: configuration.ServerSelection{ selection: configuration.ServerSelection{
Group: "1", Groups: []string{"Premium UDP Europe"},
}, },
filteredServers: []models.CyberghostServer{ filteredServers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium UDP Europe"},
{Region: "b", Group: "1"}, {Region: "b", Group: "Premium UDP Europe"},
}, },
}, },
"servers with bad group filter": {
servers: []models.CyberghostServer{
{Region: "a", Group: "Premium TCP Europe"},
{Region: "b", Group: "Premium TCP Europe"},
{Region: "c", Group: "Premium UDP Europe"},
{Region: "d", Group: "Premium UDP Europe"},
},
selection: configuration.ServerSelection{
Groups: []string{"Premium TCP Europe"},
},
err: errors.New("server group does not match protocol: group Premium TCP Europe for protocol UDP"),
},
"servers with regions and group filter": { "servers with regions and group filter": {
servers: []models.CyberghostServer{ servers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium UDP Europe"},
{Region: "b", Group: "1"}, {Region: "b", Group: "Premium TCP Europe"},
{Region: "c", Group: "2"}, {Region: "c", Group: "Premium UDP Asia"},
{Region: "d", Group: "2"}, {Region: "d", Group: "Premium TCP Asia"},
}, },
selection: configuration.ServerSelection{ selection: configuration.ServerSelection{
Regions: []string{"a", "c"}, Regions: []string{"a", "c"},
Group: "1", Groups: []string{"Premium UDP Europe"},
}, },
filteredServers: []models.CyberghostServer{ filteredServers: []models.CyberghostServer{
{Region: "a", Group: "1"}, {Region: "a", Group: "Premium UDP Europe"},
}, },
}, },
"servers with hostnames filter": { "servers with hostnames filter": {
servers: []models.CyberghostServer{ servers: []models.CyberghostServer{
{Hostname: "a"}, {Hostname: "a", Group: "Premium UDP Asia"},
{Hostname: "b"}, {Hostname: "b", Group: "Premium UDP Asia"},
{Hostname: "c"}, {Hostname: "c", Group: "Premium UDP Asia"},
}, },
selection: configuration.ServerSelection{ selection: configuration.ServerSelection{
Hostnames: []string{"a", "c"}, Hostnames: []string{"a", "c"},
}, },
filteredServers: []models.CyberghostServer{ filteredServers: []models.CyberghostServer{
{Hostname: "a"}, {Hostname: "a", Group: "Premium UDP Asia"},
{Hostname: "c"}, {Hostname: "c", Group: "Premium UDP Asia"},
}, },
}, },
} }
@@ -113,3 +138,25 @@ func Test_Cyberghost_filterServers(t *testing.T) {
}) })
} }
} }
func Test_tcpGroupChoices(t *testing.T) {
t.Parallel()
expected := []string{
"Premium TCP Asia", "Premium TCP Europe", "Premium TCP USA",
}
choices := tcpGroupChoices()
assert.Equal(t, expected, choices)
}
func Test_udpGroupChoices(t *testing.T) {
t.Parallel()
expected := []string{
"Premium UDP Asia", "Premium UDP Europe", "Premium UDP USA",
}
choices := udpGroupChoices()
assert.Equal(t, expected, choices)
}

View File

@@ -25,8 +25,13 @@ func NoServerFoundError(selection configuration.ServerSelection) (err error) {
} }
messageParts = append(messageParts, "protocol "+protocol) messageParts = append(messageParts, "protocol "+protocol)
if selection.Group != "" { switch len(selection.Countries) {
part := "group " + selection.Group case 0:
case 1:
part := "group " + selection.Groups[0]
messageParts = append(messageParts, part)
default:
part := "groups " + commaJoin(selection.Groups)
messageParts = append(messageParts, part) messageParts = append(messageParts, part)
} }