From 982536e9e831aae152f5fbdb587dafff9c6de0c9 Mon Sep 17 00:00:00 2001 From: "Quentin McGaw (desktop)" Date: Sat, 31 Jul 2021 14:53:34 +0000 Subject: [PATCH] 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 --- internal/configuration/cyberghost.go | 7 +- internal/configuration/openvpn_test.go | 2 +- internal/configuration/provider_test.go | 4 +- internal/configuration/selection.go | 2 +- internal/constants/cyberghost.go | 19 +++- internal/constants/cyberghost_test.go | 18 ++++ internal/provider/cyberghost/filter.go | 73 ++++++++++++- internal/provider/cyberghost/filter_test.go | 113 ++++++++++++++------ internal/provider/utils/formatting.go | 9 +- 9 files changed, 199 insertions(+), 48 deletions(-) create mode 100644 internal/constants/cyberghost_test.go diff --git a/internal/configuration/cyberghost.go b/internal/configuration/cyberghost.go index 8131aec7..41b975fb 100644 --- a/internal/configuration/cyberghost.go +++ b/internal/configuration/cyberghost.go @@ -4,11 +4,10 @@ import ( "fmt" "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/golibs/params" ) 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 { lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) @@ -52,8 +51,8 @@ func (settings *Provider) readCyberghost(r reader) (err error) { return err } - settings.ServerSelection.Group, err = r.env.Inside("CYBERGHOST_GROUP", - constants.CyberghostGroupChoices(), params.Default("Premium UDP Europe")) + settings.ServerSelection.Groups, err = r.env.CSVInside("CYBERGHOST_GROUP", + constants.CyberghostGroupChoices()) if err != nil { return fmt.Errorf("environment variable CYBERGHOST_GROUP: %w", err) } diff --git a/internal/configuration/openvpn_test.go b/internal/configuration/openvpn_test.go index 29ce3212..b75e4e24 100644 --- a/internal/configuration/openvpn_test.go +++ b/internal/configuration/openvpn_test.go @@ -33,7 +33,7 @@ func Test_OpenVPN_JSON(t *testing.T) { "server_selection": { "tcp": false, "regions": null, - "group": "", + "groups": null, "countries": null, "cities": null, "hostnames": null, diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index 7ab36992..26d2989e 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -24,7 +24,7 @@ func Test_Provider_lines(t *testing.T) { settings: Provider{ Name: constants.Cyberghost, ServerSelection: ServerSelection{ - Group: "group", + Groups: []string{"group"}, Regions: []string{"a", "El country"}, }, ExtraConfigOptions: ExtraConfigOptions{ @@ -35,7 +35,7 @@ func Test_Provider_lines(t *testing.T) { lines: []string{ "|--Cyberghost settings:", " |--Network protocol: udp", - " |--Server group: group", + " |--Server groups: group", " |--Regions: a, El country", " |--Client key is set", " |--Client certificate is set", diff --git a/internal/configuration/selection.go b/internal/configuration/selection.go index b3503df2..d776970d 100644 --- a/internal/configuration/selection.go +++ b/internal/configuration/selection.go @@ -13,7 +13,7 @@ type ServerSelection struct { //nolint:maligned Regions []string `json:"regions"` // Cyberghost - Group string `json:"group"` + Groups []string `json:"groups"` // Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited Countries []string `json:"countries"` diff --git a/internal/constants/cyberghost.go b/internal/constants/cyberghost.go index 8ecba764..53e6d1a4 100644 --- a/internal/constants/cyberghost.go +++ b/internal/constants/cyberghost.go @@ -1,6 +1,8 @@ package constants import ( + "sort" + "github.com/qdm12/gluetun/internal/models" ) @@ -20,11 +22,20 @@ func CyberghostRegionChoices() (choices []string) { func CyberghostGroupChoices() (choices []string) { servers := CyberghostServers() - choices = make([]string, len(servers)) - for i := range servers { - choices[i] = servers[i].Group + uniqueChoices := map[string]struct{}{} + for _, server := range servers { + 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) { diff --git a/internal/constants/cyberghost_test.go b/internal/constants/cyberghost_test.go new file mode 100644 index 00000000..3214be6f --- /dev/null +++ b/internal/constants/cyberghost_test.go @@ -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) +} diff --git a/internal/provider/cyberghost/filter.go b/internal/provider/cyberghost/filter.go index 7f51823b..c9d92d03 100644 --- a/internal/provider/cyberghost/filter.go +++ b/internal/provider/cyberghost/filter.go @@ -1,18 +1,41 @@ package cyberghost import ( + "errors" + "fmt" "strings" "github.com/qdm12/gluetun/internal/configuration" + "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/provider/utils" ) +var ErrGroupMismatchesProtocol = errors.New("server group does not match protocol") + func (c *Cyberghost) filterServers(selection configuration.ServerSelection) ( 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 { 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.Hostname, selection.Hostnames): default: @@ -26,3 +49,51 @@ func (c *Cyberghost) filterServers(selection configuration.ServerSelection) ( 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 +} diff --git a/internal/provider/cyberghost/filter_test.go b/internal/provider/cyberghost/filter_test.go index ae1ba355..64389d1a 100644 --- a/internal/provider/cyberghost/filter_test.go +++ b/internal/provider/cyberghost/filter_test.go @@ -21,77 +21,102 @@ func Test_Cyberghost_filterServers(t *testing.T) { "no servers": { err: errors.New("no server found: for protocol udp"), }, - "servers without filter": { + "servers without filter defaults to UDP": { servers: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "b", Group: "1"}, - {Region: "c", Group: "2"}, - {Region: "d", Group: "2"}, + {Region: "a", Group: "Premium TCP Asia"}, + {Region: "b", Group: "Premium TCP Europe"}, + {Region: "c", Group: "Premium UDP Asia"}, + {Region: "d", Group: "Premium UDP Europe"}, }, filteredServers: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "b", Group: "1"}, - {Region: "c", Group: "2"}, - {Region: "d", Group: "2"}, + {Region: "c", Group: "Premium UDP Asia"}, + {Region: "d", Group: "Premium UDP Europe"}, + }, + }, + "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: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "b", Group: "1"}, - {Region: "c", Group: "2"}, - {Region: "d", Group: "2"}, + {Region: "a", Group: "Premium UDP Asia"}, + {Region: "b", Group: "Premium UDP Asia"}, + {Region: "c", Group: "Premium UDP Asia"}, + {Region: "d", Group: "Premium UDP Asia"}, }, selection: configuration.ServerSelection{ Regions: []string{"a", "c"}, }, filteredServers: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "c", Group: "2"}, + {Region: "a", Group: "Premium UDP Asia"}, + {Region: "c", Group: "Premium UDP Asia"}, }, }, "servers with group filter": { servers: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "b", Group: "1"}, - {Region: "c", Group: "2"}, - {Region: "d", Group: "2"}, + {Region: "a", Group: "Premium UDP Europe"}, + {Region: "b", Group: "Premium UDP Europe"}, + {Region: "c", Group: "Premium TCP Europe"}, + {Region: "d", Group: "Premium TCP Europe"}, }, selection: configuration.ServerSelection{ - Group: "1", + Groups: []string{"Premium UDP Europe"}, }, filteredServers: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "b", Group: "1"}, + {Region: "a", Group: "Premium UDP Europe"}, + {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: []models.CyberghostServer{ - {Region: "a", Group: "1"}, - {Region: "b", Group: "1"}, - {Region: "c", Group: "2"}, - {Region: "d", Group: "2"}, + {Region: "a", Group: "Premium UDP Europe"}, + {Region: "b", Group: "Premium TCP Europe"}, + {Region: "c", Group: "Premium UDP Asia"}, + {Region: "d", Group: "Premium TCP Asia"}, }, selection: configuration.ServerSelection{ Regions: []string{"a", "c"}, - Group: "1", + Groups: []string{"Premium UDP Europe"}, }, filteredServers: []models.CyberghostServer{ - {Region: "a", Group: "1"}, + {Region: "a", Group: "Premium UDP Europe"}, }, }, "servers with hostnames filter": { servers: []models.CyberghostServer{ - {Hostname: "a"}, - {Hostname: "b"}, - {Hostname: "c"}, + {Hostname: "a", Group: "Premium UDP Asia"}, + {Hostname: "b", Group: "Premium UDP Asia"}, + {Hostname: "c", Group: "Premium UDP Asia"}, }, selection: configuration.ServerSelection{ Hostnames: []string{"a", "c"}, }, filteredServers: []models.CyberghostServer{ - {Hostname: "a"}, - {Hostname: "c"}, + {Hostname: "a", Group: "Premium UDP Asia"}, + {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) +} diff --git a/internal/provider/utils/formatting.go b/internal/provider/utils/formatting.go index 00fb0a22..b2827707 100644 --- a/internal/provider/utils/formatting.go +++ b/internal/provider/utils/formatting.go @@ -25,8 +25,13 @@ func NoServerFoundError(selection configuration.ServerSelection) (err error) { } messageParts = append(messageParts, "protocol "+protocol) - if selection.Group != "" { - part := "group " + selection.Group + switch len(selection.Countries) { + case 0: + case 1: + part := "group " + selection.Groups[0] + messageParts = append(messageParts, part) + default: + part := "groups " + commaJoin(selection.Groups) messageParts = append(messageParts, part) }