This commit is contained in:
Quentin McGaw
2024-08-01 07:51:35 +00:00
parent ceb6ff4ca4
commit 1a6e8d74d6
12 changed files with 119 additions and 145 deletions

View File

@@ -3,4 +3,13 @@ package vpn
const ( const (
OpenVPN = "openvpn" OpenVPN = "openvpn"
Wireguard = "wireguard" 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
}

View File

@@ -22,8 +22,8 @@ type Server struct {
Number uint16 `json:"number,omitempty"` Number uint16 `json:"number,omitempty"`
ServerName string `json:"server_name,omitempty"` ServerName string `json:"server_name,omitempty"`
Hostname string `json:"hostname,omitempty"` Hostname string `json:"hostname,omitempty"`
TCP bool `json:"tcp,omitempty"` TCP bool `json:"tcp,omitempty"` // TODO v4 rename to openvpn_tcp
UDP bool `json:"udp,omitempty"` UDP bool `json:"udp,omitempty"` // TODO v4 rename to openvpn_udp
OvpnX509 string `json:"x509,omitempty"` OvpnX509 string `json:"x509,omitempty"`
RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4 RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
MultiHop bool `json:"multihop,omitempty"` MultiHop bool `json:"multihop,omitempty"`
@@ -38,6 +38,25 @@ type Server struct {
IPs []netip.Addr `json:"ips,omitempty"` 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 ( var (
ErrVPNFieldEmpty = errors.New("vpn field is empty") ErrVPNFieldEmpty = errors.New("vpn field is empty")
ErrHostnameFieldEmpty = errors.New("hostname field is empty") ErrHostnameFieldEmpty = errors.New("hostname field is empty")
@@ -48,16 +67,18 @@ var (
) )
func (s *Server) HasMinimumInformation() (err error) { 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 { switch {
case s.VPN == "": case s.VPN == "":
return fmt.Errorf("%w", ErrVPNFieldEmpty) return fmt.Errorf("%w", ErrVPNFieldEmpty)
case len(s.IPs) == 0: case len(s.IPs) == 0:
return fmt.Errorf("%w", ErrIPsFieldEmpty) 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) 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) return fmt.Errorf("%w", ErrNoNetworkProtocol)
case s.VPN == vpn.Wireguard && s.WgPubKey == "": case isWireguard && s.WgPubKey == "":
return fmt.Errorf("%w", ErrWireguardPublicKeyEmpty) return fmt.Errorf("%w", ErrWireguardPublicKeyEmpty)
default: default:
return nil return nil

View File

@@ -68,26 +68,17 @@ func (hts hostToServerData) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
func (hts hostToServerData) toServersSlice() (servers []models.Server) { func (hts hostToServerData) toServersSlice() (servers []models.Server) {
servers = make([]models.Server, 0, 2*len(hts)) //nolint:gomnd servers = make([]models.Server, 0, 2*len(hts)) //nolint:gomnd
for hostname, serverData := range hts { for hostname, serverData := range hts {
baseServer := models.Server{ server := models.Server{
VPN: vpn.Both,
Hostname: hostname, Hostname: hostname,
Country: serverData.country, Country: serverData.country,
City: serverData.city, City: serverData.city,
IPs: serverData.ips, IPs: serverData.ips,
TCP: serverData.openvpnTCP,
UDP: serverData.openvpnUDP,
WgPubKey: "658QxufMbjOTmB61Z7f+c7Rjg7oqWLnepTalqBERjF0=",
} }
if serverData.openvpn { servers = append(servers, server)
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)
}
} }
return servers return servers
} }

View File

@@ -35,11 +35,11 @@ func (hts hostToServer) add(data serverData) (err error) {
switch data.Type { switch data.Type {
case "openvpn": case "openvpn":
server.VPN = vpn.OpenVPN server.SetVPN(vpn.OpenVPN)
server.UDP = true server.UDP = true
server.TCP = true server.TCP = true
case "wireguard": case "wireguard":
server.VPN = vpn.Wireguard server.SetVPN(vpn.Wireguard)
case "bridge": case "bridge":
// ignore bridge servers // ignore bridge servers
return nil return nil

View File

@@ -98,12 +98,6 @@ func extractServers(jsonServer serverData, groups map[uint32]groupData,
server.Number = number 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 { for _, technology := range jsonServer.Technologies {
if technology.Status != "online" { if technology.Status != "online" {
continue continue
@@ -118,33 +112,25 @@ func extractServers(jsonServer serverData, groups map[uint32]groupData,
switch technologyData.Identifier { switch technologyData.Identifier {
case "openvpn_udp", "openvpn_dedicated_udp": case "openvpn_udp", "openvpn_dedicated_udp":
openvpnFound = true server.SetVPN(vpn.OpenVPN)
openVPNServer.UDP = true server.UDP = true
case "openvpn_tcp", "openvpn_dedicated_tcp": case "openvpn_tcp", "openvpn_dedicated_tcp":
openvpnFound = true server.SetVPN(vpn.OpenVPN)
openVPNServer.TCP = true server.TCP = true
case "wireguard_udp": case "wireguard_udp":
wireguardFound = true server.WgPubKey, err = jsonServer.wireguardPublicKey(technologies)
wireguardServer.WgPubKey, err = jsonServer.wireguardPublicKey(technologies)
if err != nil { if err != nil {
warning := fmt.Sprintf("ignoring Wireguard server %s: %s", jsonServer.Name, err) warning := fmt.Sprintf("ignoring Wireguard server %s: %s", jsonServer.Name, err)
warnings = append(warnings, warning) warnings = append(warnings, warning)
wireguardFound = false
continue continue
} }
server.SetVPN(vpn.Wireguard)
default: // Ignore other technologies default: // Ignore other technologies
continue continue
} }
} }
const maxServers = 2 servers = append(servers, server)
servers = make([]models.Server, 0, maxServers)
if openvpnFound {
servers = append(servers, openVPNServer)
}
if wireguardFound {
servers = append(servers, wireguardServer)
}
return servers, warnings return servers, warnings
} }

View File

@@ -7,11 +7,12 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers" "github.com/qdm12/gluetun/internal/provider/surfshark/servers"
) )
func addServersFromAPI(ctx context.Context, client *http.Client, func addServersFromAPI(ctx context.Context, client *http.Client,
hts hostToServers) (err error) { hts hostToServer) (err error) {
data, err := fetchAPI(ctx, client) data, err := fetchAPI(ctx, client)
if err != nil { if err != nil {
return err 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 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 tcp, udp := true, true // OpenVPN servers from API supports both TCP and UDP
hts.addOpenVPN(serverData.Host, serverData.Region, serverData.Country, const wgPubKey = ""
serverData.Location, retroLoc, tcp, udp) hts.add(serverData.Host, vpn.OpenVPN, serverData.Region, serverData.Country,
serverData.Location, retroLoc, wgPubKey, tcp, udp)
if serverData.PubKey != "" { if serverData.PubKey != "" {
hts.addWireguard(serverData.Host, serverData.Region, serverData.Country, const wgTCP, wgUDP = false, false // unused
serverData.Location, retroLoc, serverData.PubKey) hts.add(serverData.Host, vpn.Wireguard, serverData.Region, serverData.Country,
serverData.Location, retroLoc, serverData.PubKey, wgTCP, wgUDP)
} }
} }

View File

@@ -24,9 +24,9 @@ func Test_addServersFromAPI(t *testing.T) {
t.Parallel() t.Parallel()
testCases := map[string]struct { testCases := map[string]struct {
hts hostToServers hts hostToServer
exchanges []httpExchange exchanges []httpExchange
expected hostToServers expected hostToServer
err error err error
}{ }{
"fetch API 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"), err: errors.New("HTTP status code not OK: 204 No Content"),
}, },
"success": { "success": {
hts: hostToServers{ hts: hostToServer{
"existinghost": []models.Server{{Hostname: "existinghost"}}, "existinghost": models.Server{Hostname: "existinghost"},
}, },
exchanges: []httpExchange{{ exchanges: []httpExchange{{
requestURL: "https://api.surfshark.com/v4/server/clusters/generic", requestURL: "https://api.surfshark.com/v4/server/clusters/generic",
@@ -61,25 +61,19 @@ func Test_addServersFromAPI(t *testing.T) {
responseStatus: http.StatusOK, responseStatus: http.StatusOK,
responseBody: io.NopCloser(strings.NewReader(`[]`)), responseBody: io.NopCloser(strings.NewReader(`[]`)),
}}, }},
expected: map[string][]models.Server{ expected: map[string]models.Server{
"existinghost": {{Hostname: "existinghost"}}, "existinghost": {Hostname: "existinghost"},
"host1": {{ "host1": {
VPN: vpn.OpenVPN, VPN: vpn.Both,
Region: "region1", Region: "region1",
Country: "country1", Country: "country1",
City: "location1", City: "location1",
Hostname: "host1", Hostname: "host1",
TCP: true, TCP: true,
UDP: true, UDP: true,
}, {
VPN: vpn.Wireguard,
Region: "region1",
Country: "country1",
City: "location1",
Hostname: "host1",
WgPubKey: "pubKeyValue", WgPubKey: "pubKeyValue",
}}, },
"host2": {{ "host2": {
VPN: vpn.OpenVPN, VPN: vpn.OpenVPN,
Region: "region2", Region: "region2",
Country: "country1", Country: "country1",
@@ -87,7 +81,7 @@ func Test_addServersFromAPI(t *testing.T) {
Hostname: "host2", Hostname: "host2",
TCP: true, TCP: true,
UDP: true, UDP: true,
}}, },
}, },
}, },
} }

View File

@@ -7,94 +7,62 @@ import (
"github.com/qdm12/gluetun/internal/models" "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, func (hts hostToServer) add(host, vpnType, region, country, city,
retroLoc string, tcp, udp bool) { retroLoc, wgPubKey string, openvpnTCP, openvpnUDP bool) {
// Check for existing server for this host and OpenVPN. server, ok := hts[host]
servers := hts[host] if !ok {
for i, existingServer := range servers { server := models.Server{
if existingServer.Hostname != host || VPN: vpnType,
existingServer.VPN != vpn.OpenVPN { Region: region,
continue Country: country,
} City: city,
RetroLoc: retroLoc,
// Update OpenVPN supported protocols and return Hostname: host,
if !existingServer.TCP { WgPubKey: wgPubKey,
servers[i].TCP = tcp TCP: openvpnTCP,
} UDP: openvpnUDP,
if !existingServer.UDP {
servers[i].UDP = udp
} }
hts[host] = server
return return
} }
server := models.Server{ server.SetVPN(vpnType)
VPN: vpn.OpenVPN, if vpnType == vpn.OpenVPN {
Region: region, server.TCP = server.TCP || openvpnTCP
Country: country, server.UDP = server.UDP || openvpnUDP
City: city, } else if wgPubKey != "" {
RetroLoc: retroLoc, server.WgPubKey = wgPubKey
Hostname: host,
TCP: tcp,
UDP: udp,
} }
hts[host] = append(servers, server)
hts[host] = server
} }
func (hts hostToServers) addWireguard(host, region, country, city, retroLoc, func (hts hostToServer) toHostsSlice() (hosts []string) {
wgPubKey string) { hosts = make([]string, 0, len(hts))
// 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))
for host := range hts { for host := range hts {
hosts = append(hosts, host) hosts = append(hosts, host)
} }
return hosts return hosts
} }
func (hts hostToServers) adaptWithIPs(hostToIPs map[string][]netip.Addr) { func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
for host, IPs := range hostToIPs { for host, server := range hts {
servers := hts[host] ips := hostToIPs[host]
for i := range servers { if len(ips) == 0 {
servers[i].IPs = IPs
}
hts[host] = servers
}
for host, servers := range hts {
if len(servers[0].IPs) == 0 {
delete(hts, host) delete(hts, host)
continue
} }
server.IPs = ips
hts[host] = server
} }
} }
func (hts hostToServers) toServersSlice() (servers []models.Server) { func (hts hostToServer) toServersSlice() (servers []models.Server) {
const vpnServerTypes = 2 // OpenVPN + Wireguard servers = make([]models.Server, 0, len(hts))
servers = make([]models.Server, 0, vpnServerTypes*len(hts)) for _, server := range hts {
for _, serversForHost := range hts { servers = append(servers, server)
servers = append(servers, serversForHost...)
} }
return servers return servers
} }

View File

@@ -1,11 +1,12 @@
package updater package updater
import ( import (
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers" "github.com/qdm12/gluetun/internal/provider/surfshark/servers"
) )
// getRemainingServers finds extra servers not found in the API or in the ZIP file. // 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() locationData := servers.LocationData()
hostnameToLocationLeft := hostToLocation(locationData) hostnameToLocationLeft := hostToLocation(locationData)
for _, hostnameDone := range hts.toHostsSlice() { for _, hostnameDone := range hts.toHostsSlice() {
@@ -15,7 +16,8 @@ func getRemainingServers(hts hostToServers) {
for hostname, locationData := range hostnameToLocationLeft { for hostname, locationData := range hostnameToLocationLeft {
// we assume the OpenVPN server supports both TCP and UDP // we assume the OpenVPN server supports both TCP and UDP
const tcp, udp = true, true const tcp, udp = true, true
hts.addOpenVPN(hostname, locationData.Region, locationData.Country, const wgPubKey = ""
locationData.City, locationData.RetroLoc, tcp, udp) hts.add(hostname, vpn.OpenVPN, locationData.Region, locationData.Country,
locationData.City, locationData.RetroLoc, wgPubKey, tcp, udp)
} }
} }

View File

@@ -11,7 +11,7 @@ import (
func (u *Updater) FetchServers(ctx context.Context, minServers int) ( func (u *Updater) FetchServers(ctx context.Context, minServers int) (
servers []models.Server, err error) { servers []models.Server, err error) {
hts := make(hostToServers) hts := make(hostToServer)
err = addServersFromAPI(ctx, u.client, hts) err = addServersFromAPI(ctx, u.client, hts)
if err != nil { if err != nil {

View File

@@ -4,13 +4,14 @@ import (
"context" "context"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/provider/common" "github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers" "github.com/qdm12/gluetun/internal/provider/surfshark/servers"
"github.com/qdm12/gluetun/internal/updater/openvpn" "github.com/qdm12/gluetun/internal/updater/openvpn"
) )
func addOpenVPNServersFromZip(ctx context.Context, func addOpenVPNServersFromZip(ctx context.Context,
unzipper common.Unzipper, hts hostToServers) ( unzipper common.Unzipper, hts hostToServer) (
warnings []string, err error) { warnings []string, err error) {
const url = "https://my.surfshark.com/vpn/api/v1/server/configurations" const url = "https://my.surfshark.com/vpn/api/v1/server/configurations"
contents, err := unzipper.FetchAndExtract(ctx, url) contents, err := unzipper.FetchAndExtract(ctx, url)
@@ -66,8 +67,9 @@ func addOpenVPNServersFromZip(ctx context.Context,
continue continue
} }
hts.addOpenVPN(host, data.Region, data.Country, data.City, const wgPubKey = ""
data.RetroLoc, tcp, udp) hts.add(host, vpn.OpenVPN, data.Region, data.Country, data.City,
data.RetroLoc, wgPubKey, tcp, udp)
} }
return warnings, nil return warnings, nil

View File

@@ -50,11 +50,11 @@ func filterServer(server models.Server,
selection settings.ServerSelection) (filtered bool) { selection settings.ServerSelection) (filtered bool) {
// Note each condition is split to make sure // Note each condition is split to make sure
// we have full testing coverage. // we have full testing coverage.
if server.VPN != selection.VPN { if server.VPN != vpn.Both && server.VPN != selection.VPN {
return true return true
} }
if server.VPN != vpn.Wireguard && if selection.VPN == vpn.OpenVPN &&
filterByProtocol(selection, server.TCP, server.UDP) { filterByProtocol(selection, server.TCP, server.UDP) {
return true return true
} }
@@ -119,8 +119,6 @@ func filterServer(server models.Server,
return true return true
} }
// TODO filter port forward server for PIA
return false return false
} }