package updater import ( "context" "errors" "fmt" "sort" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/provider/common" ) var ErrNotIPv4 = errors.New("IP address is not IPv4") func (u *Updater) FetchServers(ctx context.Context, minServers int) ( servers []models.Server, err error, ) { const limit = 0 data, err := fetchAPI(ctx, u.client, limit) if err != nil { return nil, err } servers = make([]models.Server, 0, len(data.Servers)) groups, services, locations, technologies := data.idToData() for _, jsonServer := range data.Servers { newServers, warnings := extractServers(jsonServer, groups, services, locations, technologies) for _, warning := range warnings { u.warner.Warn(warning) } servers = append(servers, newServers...) } if len(servers) < minServers { return nil, fmt.Errorf("%w: %d and expected at least %d", common.ErrNotEnoughServers, len(servers), minServers) } sort.Sort(models.SortableServers(servers)) return servers, nil } func extractServers(jsonServer serverData, groups map[uint32]groupData, services map[uint32]serviceData, locations map[uint32]locationData, technologies map[uint32]technologyData) (servers []models.Server, warnings []string, ) { ignoreReason := "" switch { case jsonServer.Status != "online": ignoreReason = "status is " + jsonServer.Status case len(jsonServer.LocationIDs) == 0: ignoreReason = "no location" case len(jsonServer.IPs) == 0: ignoreReason = "no IP address" case !jsonServer.hasVPNService(services): ignoreReason = "no VPN service" } if ignoreReason != "" { warning := fmt.Sprintf("ignoring server %s: %s", jsonServer.Name, ignoreReason) return nil, []string{warning} } location, ok := locations[jsonServer.LocationIDs[0]] if !ok { warning := fmt.Sprintf("location with id %d not found in %v", jsonServer.LocationIDs[0], locations) return nil, []string{warning} } region := jsonServer.region(groups) if region == "" { warning := fmt.Sprintf("no region found for server %s", jsonServer.Name) return nil, []string{warning} } server := models.Server{ Country: location.Country.Name, Region: region, City: location.Country.City.Name, Categories: jsonServer.categories(groups), Hostname: jsonServer.Hostname, IPs: jsonServer.ips(), } number, err := parseServerName(jsonServer.Name) switch { case errors.Is(err, ErrNoIDInServerName): warning := fmt.Sprintf("%s - leaving server number as 0", err) warnings = append(warnings, warning) case err != nil: warning := fmt.Sprintf("failed parsing server name: %s", err) return nil, []string{warning} default: // no error 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 } technologyData, ok := technologies[technology.ID] if !ok { warning := fmt.Sprintf("technology with id %d not found in %v", technology.ID, technologies) warnings = append(warnings, warning) continue } switch technologyData.Identifier { case "openvpn_udp", "openvpn_dedicated_udp": openvpnFound = true openVPNServer.UDP = true case "openvpn_tcp", "openvpn_dedicated_tcp": openvpnFound = true openVPNServer.TCP = true case "wireguard_udp": wireguardFound = true wireguardServer.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 } 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) } return servers, warnings }