Windscribe API and more servers filter options, fixes #197 (#282)

- Use Windscribe API to fetch servers information
- More data on servers about region, city and hostname
- Add optional server filters with `REGION`, `CITY` and `HOSTNAME` csv environment variables
This commit is contained in:
Quentin McGaw
2020-11-04 20:38:35 -05:00
committed by GitHub
parent 3b04677f8f
commit 31883f9adb
15 changed files with 430 additions and 203 deletions

View File

@@ -2,14 +2,19 @@ package updater
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"sort"
"time"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
)
func (u *updater) updateWindscribe(ctx context.Context) (err error) {
servers, err := findWindscribeServers(ctx, u.lookupIP)
servers, err := findWindscribeServers(ctx, u.client)
if err != nil {
return fmt.Errorf("cannot update Windscribe servers: %w", err)
}
@@ -21,46 +26,53 @@ func (u *updater) updateWindscribe(ctx context.Context) (err error) {
return nil
}
func findWindscribeServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.WindscribeServer, err error) {
allCountryCodes := getCountryCodes()
windscribeCountryCodes := getWindscribeSubdomainToRegion()
possibleCountryCodes := mergeCountryCodes(windscribeCountryCodes, allCountryCodes)
const domain = "windscribe.com"
for countryCode, region := range possibleCountryCodes {
if err := ctx.Err(); err != nil {
return nil, err
func findWindscribeServers(ctx context.Context, client network.Client) (servers []models.WindscribeServer, err error) {
const baseURL = "https://assets.windscribe.com/serverlist/mob-v2/1/"
cacheBreaker := time.Now().Unix()
url := fmt.Sprintf("%s%d", baseURL, cacheBreaker)
content, status, err := client.Get(ctx, url)
if err != nil {
return nil, err
} else if status != http.StatusOK {
return nil, fmt.Errorf(http.StatusText(status))
}
var jsonData struct {
Data []struct {
Region string `json:"name"`
Groups []struct {
City string `json:"city"`
Nodes []struct {
Hostname string `json:"hostname"`
OpenvpnIP net.IP `json:"ip2"`
} `json:"nodes"`
} `json:"groups"`
} `json:"data"`
}
if err := json.Unmarshal(content, &jsonData); err != nil {
return nil, err
}
for _, regionBlock := range jsonData.Data {
region := regionBlock.Region
for _, group := range regionBlock.Groups {
city := group.City
for _, node := range group.Nodes {
server := models.WindscribeServer{
Region: region,
City: city,
Hostname: node.Hostname,
IP: node.OpenvpnIP,
}
servers = append(servers, server)
}
}
host := countryCode + "." + domain
const repetitions = 5
ips, err := resolveRepeat(ctx, lookupIP, host, repetitions)
if err != nil || len(ips) == 0 {
continue
}
servers = append(servers, models.WindscribeServer{
Region: region,
IPs: ips,
})
}
sort.Slice(servers, func(i, j int) bool {
return servers[i].Region < servers[j].Region
return servers[i].Region+servers[i].City+servers[i].Hostname <
servers[j].Region+servers[j].City+servers[j].Hostname
})
return servers, nil
}
func mergeCountryCodes(base, extend map[string]string) (merged map[string]string) {
merged = make(map[string]string, len(base))
for countryCode, region := range base {
merged[countryCode] = region
}
for countryCode := range base {
delete(extend, countryCode)
}
for countryCode, region := range extend {
merged[countryCode] = region
}
return merged
}
func stringifyWindscribeServers(servers []models.WindscribeServer) (s string) {
s = "func WindscribeServers() []models.WindscribeServer {\n"
s += " return []models.WindscribeServer{\n"
@@ -71,77 +83,3 @@ func stringifyWindscribeServers(servers []models.WindscribeServer) (s string) {
s += "}"
return s
}
func getWindscribeSubdomainToRegion() map[string]string {
return map[string]string{
"al": "Albania",
"ar": "Argentina",
"au": "Australia",
"at": "Austria",
"az": "Azerbaijan",
"be": "Belgium",
"ba": "Bosnia",
"br": "Brazil",
"bg": "Bulgaria",
"ca": "Canada East",
"ca-west": "Canada West",
"co": "Colombia",
"hr": "Croatia",
"cy": "Cyprus",
"cz": "Czech republic",
"dk": "Denmark",
"ee": "Estonia",
"aq": "Fake antarctica",
"fi": "Finland",
"fr": "France",
"ge": "Georgia",
"de": "Germany",
"gr": "Greece",
"hk": "Hong kong",
"hu": "Hungary",
"is": "Iceland",
"in": "India",
"id": "Indonesia",
"ie": "Ireland",
"il": "Israel",
"it": "Italy",
"jp": "Japan",
"lv": "Latvia",
"lt": "Lithuania",
"mk": "Macedonia",
"my": "Malaysia",
"mx": "Mexico",
"md": "Moldova",
"nl": "Netherlands",
"nz": "New zealand",
"no": "Norway",
"ph": "Philippines",
"pl": "Poland",
"pt": "Portugal",
"ro": "Romania",
"ru": "Russia",
"rs": "Serbia",
"sg": "Singapore",
"sk": "Slovakia",
"si": "Slovenia",
"za": "South Africa",
"kr": "South Korea",
"es": "Spain",
"se": "Sweden",
"ch": "Switzerland",
"th": "Thailand",
"tn": "Tunisia",
"tr": "Turkey",
"ua": "Ukraine",
"ae": "United Arab Emirates",
"uk": "United Kingdom",
"us-central": "US Central",
"us-east": "US East",
"us-west": "US West",
"vn": "Vietnam",
"wf-ca": "Windflix CA",
"wf-jp": "Windflix JP",
"wf-uk": "Windflix UK",
"wf-us": "Windflix US",
}
}