diff --git a/internal/constants/vpn/protocol.go b/internal/constants/vpn/protocol.go index 1fd36b88..d3706559 100644 --- a/internal/constants/vpn/protocol.go +++ b/internal/constants/vpn/protocol.go @@ -3,4 +3,13 @@ package vpn const ( OpenVPN = "openvpn" Wireguard = "wireguard" + Both = "openvpn+wireguard" ) + +func IsWireguard(s string) bool { + return s == Wireguard || s == Both +} + +func IsOpenVPN(s string) bool { + return s == OpenVPN || s == Both +} diff --git a/internal/models/server.go b/internal/models/server.go index 1ae92fb6..a46f6bfa 100644 --- a/internal/models/server.go +++ b/internal/models/server.go @@ -22,8 +22,8 @@ type Server struct { Number uint16 `json:"number,omitempty"` ServerName string `json:"server_name,omitempty"` Hostname string `json:"hostname,omitempty"` - TCP bool `json:"tcp,omitempty"` - UDP bool `json:"udp,omitempty"` + TCP bool `json:"tcp,omitempty"` // TODO v4 rename to openvpn_tcp + UDP bool `json:"udp,omitempty"` // TODO v4 rename to openvpn_udp OvpnX509 string `json:"x509,omitempty"` RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4 MultiHop bool `json:"multihop,omitempty"` @@ -38,6 +38,25 @@ type Server struct { IPs []netip.Addr `json:"ips,omitempty"` } +func (s *Server) SetVPN(vpnType string) { + switch s.VPN { + case "": + s.VPN = vpnType + case vpn.Both: + return + case vpn.OpenVPN: + if vpnType == vpn.Wireguard { + s.VPN = vpn.Both + } + case vpn.Wireguard: + if vpnType == vpn.OpenVPN { + s.VPN = vpn.Both + } + default: + panic(fmt.Sprintf("VPN type %q not supported", s.VPN)) + } +} + var ( ErrVPNFieldEmpty = errors.New("vpn field is empty") ErrHostnameFieldEmpty = errors.New("hostname field is empty") @@ -48,16 +67,18 @@ var ( ) func (s *Server) HasMinimumInformation() (err error) { + isOpenVPN := s.VPN == vpn.OpenVPN || s.VPN == vpn.Both + isWireguard := s.VPN == vpn.Wireguard || s.VPN == vpn.Both switch { case s.VPN == "": return fmt.Errorf("%w", ErrVPNFieldEmpty) case len(s.IPs) == 0: return fmt.Errorf("%w", ErrIPsFieldEmpty) - case s.VPN == vpn.Wireguard && (s.TCP || s.UDP): + case isWireguard && !isOpenVPN && (s.TCP || s.UDP): return fmt.Errorf("%w", ErrNetworkProtocolSet) - case s.VPN == vpn.OpenVPN && !s.TCP && !s.UDP: + case isOpenVPN && !s.TCP && !s.UDP: return fmt.Errorf("%w", ErrNoNetworkProtocol) - case s.VPN == vpn.Wireguard && s.WgPubKey == "": + case isWireguard && s.WgPubKey == "": return fmt.Errorf("%w", ErrWireguardPublicKeyEmpty) default: return nil diff --git a/internal/provider/fastestvpn/updater/hosttoserver.go b/internal/provider/fastestvpn/updater/hosttoserver.go index 3510f1f8..ef0ee9c8 100644 --- a/internal/provider/fastestvpn/updater/hosttoserver.go +++ b/internal/provider/fastestvpn/updater/hosttoserver.go @@ -68,26 +68,17 @@ func (hts hostToServerData) adaptWithIPs(hostToIPs map[string][]netip.Addr) { func (hts hostToServerData) toServersSlice() (servers []models.Server) { servers = make([]models.Server, 0, 2*len(hts)) //nolint:gomnd for hostname, serverData := range hts { - baseServer := models.Server{ + server := models.Server{ + VPN: vpn.Both, Hostname: hostname, Country: serverData.country, City: serverData.city, IPs: serverData.ips, + TCP: serverData.openvpnTCP, + UDP: serverData.openvpnUDP, + WgPubKey: "658QxufMbjOTmB61Z7f+c7Rjg7oqWLnepTalqBERjF0=", } - if serverData.openvpn { - openvpnServer := baseServer - openvpnServer.VPN = vpn.OpenVPN - openvpnServer.TCP = serverData.openvpnTCP - openvpnServer.UDP = serverData.openvpnUDP - servers = append(servers, openvpnServer) - } - if serverData.wireguard { - wireguardServer := baseServer - wireguardServer.VPN = vpn.Wireguard - const wireguardPublicKey = "658QxufMbjOTmB61Z7f+c7Rjg7oqWLnepTalqBERjF0=" - wireguardServer.WgPubKey = wireguardPublicKey - servers = append(servers, wireguardServer) - } + servers = append(servers, server) } return servers } diff --git a/internal/provider/mullvad/updater/hosttoserver.go b/internal/provider/mullvad/updater/hosttoserver.go index 66771106..4aeaa728 100644 --- a/internal/provider/mullvad/updater/hosttoserver.go +++ b/internal/provider/mullvad/updater/hosttoserver.go @@ -35,11 +35,11 @@ func (hts hostToServer) add(data serverData) (err error) { switch data.Type { case "openvpn": - server.VPN = vpn.OpenVPN + server.SetVPN(vpn.OpenVPN) server.UDP = true server.TCP = true case "wireguard": - server.VPN = vpn.Wireguard + server.SetVPN(vpn.Wireguard) case "bridge": // ignore bridge servers return nil diff --git a/internal/provider/nordvpn/updater/servers.go b/internal/provider/nordvpn/updater/servers.go index b5427445..30abeaed 100644 --- a/internal/provider/nordvpn/updater/servers.go +++ b/internal/provider/nordvpn/updater/servers.go @@ -98,12 +98,6 @@ func extractServers(jsonServer serverData, groups map[uint32]groupData, server.Number = number } - var wireguardFound, openvpnFound bool - wireguardServer := server - wireguardServer.VPN = vpn.Wireguard - openVPNServer := server // accumulate UDP+TCP technologies - openVPNServer.VPN = vpn.OpenVPN - for _, technology := range jsonServer.Technologies { if technology.Status != "online" { continue @@ -118,33 +112,25 @@ func extractServers(jsonServer serverData, groups map[uint32]groupData, switch technologyData.Identifier { case "openvpn_udp", "openvpn_dedicated_udp": - openvpnFound = true - openVPNServer.UDP = true + server.SetVPN(vpn.OpenVPN) + server.UDP = true case "openvpn_tcp", "openvpn_dedicated_tcp": - openvpnFound = true - openVPNServer.TCP = true + server.SetVPN(vpn.OpenVPN) + server.TCP = true case "wireguard_udp": - wireguardFound = true - wireguardServer.WgPubKey, err = jsonServer.wireguardPublicKey(technologies) + server.WgPubKey, err = jsonServer.wireguardPublicKey(technologies) if err != nil { warning := fmt.Sprintf("ignoring Wireguard server %s: %s", jsonServer.Name, err) warnings = append(warnings, warning) - wireguardFound = false continue } + server.SetVPN(vpn.Wireguard) default: // Ignore other technologies continue } } - const maxServers = 2 - servers = make([]models.Server, 0, maxServers) - if openvpnFound { - servers = append(servers, openVPNServer) - } - if wireguardFound { - servers = append(servers, wireguardServer) - } + servers = append(servers, server) return servers, warnings } diff --git a/internal/provider/surfshark/updater/api.go b/internal/provider/surfshark/updater/api.go index 82c69e5b..9fd5ba98 100644 --- a/internal/provider/surfshark/updater/api.go +++ b/internal/provider/surfshark/updater/api.go @@ -7,11 +7,12 @@ import ( "fmt" "net/http" + "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/provider/surfshark/servers" ) func addServersFromAPI(ctx context.Context, client *http.Client, - hts hostToServers) (err error) { + hts hostToServer) (err error) { data, err := fetchAPI(ctx, client) if err != nil { return err @@ -25,12 +26,14 @@ func addServersFromAPI(ctx context.Context, client *http.Client, retroLoc := locationData.RetroLoc // empty string if the host has no retro-compatible region tcp, udp := true, true // OpenVPN servers from API supports both TCP and UDP - hts.addOpenVPN(serverData.Host, serverData.Region, serverData.Country, - serverData.Location, retroLoc, tcp, udp) + const wgPubKey = "" + hts.add(serverData.Host, vpn.OpenVPN, serverData.Region, serverData.Country, + serverData.Location, retroLoc, wgPubKey, tcp, udp) if serverData.PubKey != "" { - hts.addWireguard(serverData.Host, serverData.Region, serverData.Country, - serverData.Location, retroLoc, serverData.PubKey) + const wgTCP, wgUDP = false, false // unused + hts.add(serverData.Host, vpn.Wireguard, serverData.Region, serverData.Country, + serverData.Location, retroLoc, serverData.PubKey, wgTCP, wgUDP) } } diff --git a/internal/provider/surfshark/updater/api_test.go b/internal/provider/surfshark/updater/api_test.go index 3eb05ba4..09898c0d 100644 --- a/internal/provider/surfshark/updater/api_test.go +++ b/internal/provider/surfshark/updater/api_test.go @@ -24,9 +24,9 @@ func Test_addServersFromAPI(t *testing.T) { t.Parallel() testCases := map[string]struct { - hts hostToServers + hts hostToServer exchanges []httpExchange - expected hostToServers + expected hostToServer err error }{ "fetch API error": { @@ -37,8 +37,8 @@ func Test_addServersFromAPI(t *testing.T) { err: errors.New("HTTP status code not OK: 204 No Content"), }, "success": { - hts: hostToServers{ - "existinghost": []models.Server{{Hostname: "existinghost"}}, + hts: hostToServer{ + "existinghost": models.Server{Hostname: "existinghost"}, }, exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", @@ -61,25 +61,19 @@ func Test_addServersFromAPI(t *testing.T) { responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }}, - expected: map[string][]models.Server{ - "existinghost": {{Hostname: "existinghost"}}, - "host1": {{ - VPN: vpn.OpenVPN, + expected: map[string]models.Server{ + "existinghost": {Hostname: "existinghost"}, + "host1": { + VPN: vpn.Both, Region: "region1", Country: "country1", City: "location1", Hostname: "host1", TCP: true, UDP: true, - }, { - VPN: vpn.Wireguard, - Region: "region1", - Country: "country1", - City: "location1", - Hostname: "host1", WgPubKey: "pubKeyValue", - }}, - "host2": {{ + }, + "host2": { VPN: vpn.OpenVPN, Region: "region2", Country: "country1", @@ -87,7 +81,7 @@ func Test_addServersFromAPI(t *testing.T) { Hostname: "host2", TCP: true, UDP: true, - }}, + }, }, }, } diff --git a/internal/provider/surfshark/updater/hosttoserver.go b/internal/provider/surfshark/updater/hosttoserver.go index 6c762a67..0e335c99 100644 --- a/internal/provider/surfshark/updater/hosttoserver.go +++ b/internal/provider/surfshark/updater/hosttoserver.go @@ -7,94 +7,62 @@ import ( "github.com/qdm12/gluetun/internal/models" ) -type hostToServers map[string][]models.Server +type hostToServer map[string]models.Server -func (hts hostToServers) addOpenVPN(host, region, country, city, - retroLoc string, tcp, udp bool) { - // Check for existing server for this host and OpenVPN. - servers := hts[host] - for i, existingServer := range servers { - if existingServer.Hostname != host || - existingServer.VPN != vpn.OpenVPN { - continue - } - - // Update OpenVPN supported protocols and return - if !existingServer.TCP { - servers[i].TCP = tcp - } - if !existingServer.UDP { - servers[i].UDP = udp +func (hts hostToServer) add(host, vpnType, region, country, city, + retroLoc, wgPubKey string, openvpnTCP, openvpnUDP bool) { + server, ok := hts[host] + if !ok { + server := models.Server{ + VPN: vpnType, + Region: region, + Country: country, + City: city, + RetroLoc: retroLoc, + Hostname: host, + WgPubKey: wgPubKey, + TCP: openvpnTCP, + UDP: openvpnUDP, } + hts[host] = server return } - server := models.Server{ - VPN: vpn.OpenVPN, - Region: region, - Country: country, - City: city, - RetroLoc: retroLoc, - Hostname: host, - TCP: tcp, - UDP: udp, + server.SetVPN(vpnType) + if vpnType == vpn.OpenVPN { + server.TCP = server.TCP || openvpnTCP + server.UDP = server.UDP || openvpnUDP + } else if wgPubKey != "" { + server.WgPubKey = wgPubKey } - hts[host] = append(servers, server) + + hts[host] = server } -func (hts hostToServers) addWireguard(host, region, country, city, retroLoc, - wgPubKey string) { - // Check for existing server for this host and Wireguard. - servers := hts[host] - for _, existingServer := range servers { - if existingServer.Hostname == host && - existingServer.VPN == vpn.Wireguard { - // No update necessary for Wireguard - return - } - } - - server := models.Server{ - VPN: vpn.Wireguard, - Region: region, - Country: country, - City: city, - RetroLoc: retroLoc, - Hostname: host, - WgPubKey: wgPubKey, - } - hts[host] = append(servers, server) -} - -func (hts hostToServers) toHostsSlice() (hosts []string) { - const vpnServerTypes = 2 // OpenVPN + Wireguard - hosts = make([]string, 0, vpnServerTypes*len(hts)) +func (hts hostToServer) toHostsSlice() (hosts []string) { + hosts = make([]string, 0, len(hts)) for host := range hts { hosts = append(hosts, host) } return hosts } -func (hts hostToServers) adaptWithIPs(hostToIPs map[string][]netip.Addr) { - for host, IPs := range hostToIPs { - servers := hts[host] - for i := range servers { - servers[i].IPs = IPs - } - hts[host] = servers - } - for host, servers := range hts { - if len(servers[0].IPs) == 0 { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { + for host, server := range hts { + ips := hostToIPs[host] + if len(ips) == 0 { delete(hts, host) + continue } + server.IPs = ips + hts[host] = server } } -func (hts hostToServers) toServersSlice() (servers []models.Server) { - const vpnServerTypes = 2 // OpenVPN + Wireguard - servers = make([]models.Server, 0, vpnServerTypes*len(hts)) - for _, serversForHost := range hts { - servers = append(servers, serversForHost...) +func (hts hostToServer) toServersSlice() (servers []models.Server) { + servers = make([]models.Server, 0, len(hts)) + for _, server := range hts { + servers = append(servers, server) } return servers } diff --git a/internal/provider/surfshark/updater/remaining.go b/internal/provider/surfshark/updater/remaining.go index 56c19138..792d1d1e 100644 --- a/internal/provider/surfshark/updater/remaining.go +++ b/internal/provider/surfshark/updater/remaining.go @@ -1,11 +1,12 @@ package updater import ( + "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/provider/surfshark/servers" ) // getRemainingServers finds extra servers not found in the API or in the ZIP file. -func getRemainingServers(hts hostToServers) { +func getRemainingServers(hts hostToServer) { locationData := servers.LocationData() hostnameToLocationLeft := hostToLocation(locationData) for _, hostnameDone := range hts.toHostsSlice() { @@ -15,7 +16,8 @@ func getRemainingServers(hts hostToServers) { for hostname, locationData := range hostnameToLocationLeft { // we assume the OpenVPN server supports both TCP and UDP const tcp, udp = true, true - hts.addOpenVPN(hostname, locationData.Region, locationData.Country, - locationData.City, locationData.RetroLoc, tcp, udp) + const wgPubKey = "" + hts.add(hostname, vpn.OpenVPN, locationData.Region, locationData.Country, + locationData.City, locationData.RetroLoc, wgPubKey, tcp, udp) } } diff --git a/internal/provider/surfshark/updater/servers.go b/internal/provider/surfshark/updater/servers.go index 7fe7da08..5c8bb0d1 100644 --- a/internal/provider/surfshark/updater/servers.go +++ b/internal/provider/surfshark/updater/servers.go @@ -11,7 +11,7 @@ import ( func (u *Updater) FetchServers(ctx context.Context, minServers int) ( servers []models.Server, err error) { - hts := make(hostToServers) + hts := make(hostToServer) err = addServersFromAPI(ctx, u.client, hts) if err != nil { diff --git a/internal/provider/surfshark/updater/zip.go b/internal/provider/surfshark/updater/zip.go index 8d75ca71..6a36827c 100644 --- a/internal/provider/surfshark/updater/zip.go +++ b/internal/provider/surfshark/updater/zip.go @@ -4,13 +4,14 @@ import ( "context" "strings" + "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/provider/common" "github.com/qdm12/gluetun/internal/provider/surfshark/servers" "github.com/qdm12/gluetun/internal/updater/openvpn" ) func addOpenVPNServersFromZip(ctx context.Context, - unzipper common.Unzipper, hts hostToServers) ( + unzipper common.Unzipper, hts hostToServer) ( warnings []string, err error) { const url = "https://my.surfshark.com/vpn/api/v1/server/configurations" contents, err := unzipper.FetchAndExtract(ctx, url) @@ -66,8 +67,9 @@ func addOpenVPNServersFromZip(ctx context.Context, continue } - hts.addOpenVPN(host, data.Region, data.Country, data.City, - data.RetroLoc, tcp, udp) + const wgPubKey = "" + hts.add(host, vpn.OpenVPN, data.Region, data.Country, data.City, + data.RetroLoc, wgPubKey, tcp, udp) } return warnings, nil diff --git a/internal/storage/filter.go b/internal/storage/filter.go index f8950953..d2ec9ce5 100644 --- a/internal/storage/filter.go +++ b/internal/storage/filter.go @@ -50,11 +50,11 @@ func filterServer(server models.Server, selection settings.ServerSelection) (filtered bool) { // Note each condition is split to make sure // we have full testing coverage. - if server.VPN != selection.VPN { + if server.VPN != vpn.Both && server.VPN != selection.VPN { return true } - if server.VPN != vpn.Wireguard && + if selection.VPN == vpn.OpenVPN && filterByProtocol(selection, server.TCP, server.UDP) { return true } @@ -119,8 +119,6 @@ func filterServer(server models.Server, return true } - // TODO filter port forward server for PIA - return false }