Feature: updater changes to have more VPN IP addresses (#364)

This commit is contained in:
Quentin McGaw
2021-02-11 08:40:25 -05:00
committed by GitHub
parent f852b7789e
commit f08a03106f
7 changed files with 173 additions and 89 deletions

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"sort" "sort"
"time"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
@@ -65,7 +66,8 @@ func tryCyberghostHostname(ctx context.Context, lookupIP lookupIPFunc,
defer func() { defer func() {
<-guard <-guard
}() }()
IPs, err := resolveRepeat(ctx, lookupIP, host, 2) const repetition = 10
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition, time.Second)
if err != nil || len(IPs) == 0 { if err != nil || len(IPs) == 0 {
results <- models.CyberghostServer{} results <- models.CyberghostServer{}
return return

View File

@@ -34,10 +34,9 @@ func findPrivadoServersFromZip(ctx context.Context, client network.Client, looku
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
hosts := make([]string, 0, len(contents))
for fileName, content := range contents { for fileName, content := range contents {
if err := ctx.Err(); err != nil {
return nil, warnings, err
}
hostname, warning, err := extractHostFromOVPN(content) hostname, warning, err := extractHostFromOVPN(content)
if len(warning) > 0 { if len(warning) > 0 {
warnings = append(warnings, warning) warnings = append(warnings, warning)
@@ -45,16 +44,23 @@ func findPrivadoServersFromZip(ctx context.Context, client network.Client, looku
if err != nil { if err != nil {
return nil, warnings, fmt.Errorf("%w in %q", err, fileName) return nil, warnings, fmt.Errorf("%w in %q", err, fileName)
} }
hosts = append(hosts, hostname)
}
const repetition = 1 const repetition = 1
IPs, err := resolveRepeat(ctx, lookupIP, hostname, repetition) const timeBetween = 1
switch { const failOnErr = false
case err != nil: hostToIPs, newWarnings, _ := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
return nil, warnings, err warnings = append(warnings, newWarnings...)
case len(IPs) == 0:
for hostname, IPs := range hostToIPs {
switch len(IPs) {
case 0:
warning := fmt.Sprintf("no IP address found for host %q", hostname) warning := fmt.Sprintf("no IP address found for host %q", hostname)
warnings = append(warnings, warning) warnings = append(warnings, warning)
continue continue
case len(IPs) > 1: case 1:
default:
warning := fmt.Sprintf("more than one IP address found for host %q", hostname) warning := fmt.Sprintf("more than one IP address found for host %q", hostname)
warnings = append(warnings, warning) warnings = append(warnings, warning)
} }

View File

@@ -38,11 +38,9 @@ func findPurevpnServers(ctx context.Context, client network.Client, lookupIP loo
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
uniqueServers := map[string]models.PurevpnServer{}
hosts := make([]string, 0, len(contents))
for fileName, content := range contents { for fileName, content := range contents {
if err := ctx.Err(); err != nil {
return nil, warnings, err
}
if strings.HasSuffix(fileName, "-tcp.ovpn") { if strings.HasSuffix(fileName, "-tcp.ovpn") {
continue // only parse UDP files continue // only parse UDP files
} }
@@ -53,12 +51,20 @@ func findPurevpnServers(ctx context.Context, client network.Client, lookupIP loo
if err != nil { if err != nil {
return nil, warnings, fmt.Errorf("%w in %q", err, fileName) return nil, warnings, fmt.Errorf("%w in %q", err, fileName)
} }
const repetition = 5 hosts = append(hosts, host)
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition) }
switch {
case err != nil: const repetition = 20
const timeBetween = time.Second
const failOnErr = true
hostToIPs, _, err := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
if err != nil {
return nil, warnings, err return nil, warnings, err
case len(IPs) == 0: }
uniqueServers := make(map[string]models.PurevpnServer, len(hostToIPs))
for host, IPs := range hostToIPs {
if len(IPs) == 0 {
warning := fmt.Sprintf("no IP address found for host %q", host) warning := fmt.Sprintf("no IP address found for host %q", host)
warnings = append(warnings, warning) warnings = append(warnings, warning)
continue continue

View File

@@ -5,14 +5,16 @@ import (
"context" "context"
"net" "net"
"sort" "sort"
"time"
) )
func newResolver(resolverAddress string) *net.Resolver { func newResolver(resolverAddress string) *net.Resolver {
d := net.Dialer{}
resolverAddress = net.JoinHostPort(resolverAddress, "53")
return &net.Resolver{ return &net.Resolver{
PreferGo: true, PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{} return d.DialContext(ctx, "udp", resolverAddress)
return d.DialContext(ctx, "udp", net.JoinHostPort(resolverAddress, "53"))
}, },
} }
} }
@@ -31,35 +33,76 @@ func newLookupIP(r *net.Resolver) lookupIPFunc {
} }
} }
func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string, n int) (ips []net.IP, err error) { func parallelResolve(ctx context.Context, lookupIP lookupIPFunc, hosts []string,
foundIPs := make(chan []net.IP) repetition int, timeBetween time.Duration, failOnErr bool) (
errors := make(chan error) hostToIPs map[string][]net.IP, warnings []string, err error) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
for i := 0; i < n; i++ {
go func() { type result struct {
newIPs, err := lookupIP(ctx, host) host string
if err != nil { ips []net.IP
errors <- err
} else {
foundIPs <- newIPs
}
}()
} }
uniqueIPs := make(map[string]struct{}) results := make(chan result)
for i := 0; i < n; i++ { defer close(results)
errors := make(chan error)
defer close(errors)
for _, host := range hosts {
go func(host string) {
ips, err := resolveRepeat(ctx, lookupIP, host, repetition, timeBetween)
if err != nil {
errors <- err
return
}
results <- result{
host: host,
ips: ips,
}
}(host)
}
hostToIPs = make(map[string][]net.IP, len(hosts))
for range hosts {
select { select {
case newIPs := <-foundIPs: case newErr := <-errors:
if !failOnErr {
warnings = append(warnings, newErr.Error())
} else if err == nil {
err = newErr
cancel()
}
case r := <-results:
hostToIPs[r.host] = r.ips
}
}
return hostToIPs, warnings, err
}
func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string,
repetition int, timeBetween time.Duration) (ips []net.IP, err error) {
uniqueIPs := make(map[string]struct{})
for i := 0; i < repetition; i++ {
newIPs, err := lookupIP(ctx, host)
if err != nil {
return nil, err
}
for _, ip := range newIPs { for _, ip := range newIPs {
key := ip.String() key := ip.String()
uniqueIPs[key] = struct{}{} uniqueIPs[key] = struct{}{}
} }
case newErr := <-errors: timer := time.NewTimer(timeBetween)
if err == nil { select {
err = newErr case <-timer.C:
cancel() case <-ctx.Done():
if !timer.Stop() {
<-timer.C
} }
return nil, ctx.Err()
} }
} }
@@ -76,5 +119,5 @@ func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string, n in
return bytes.Compare(ips[i], ips[j]) < 1 return bytes.Compare(ips[i], ips[j]) < 1
}) })
return ips, err return ips, nil
} }

View File

@@ -26,7 +26,6 @@ func Test_resolveRepeat(t *testing.T) {
}, },
lookupIPErr: fmt.Errorf("feeling sick"), lookupIPErr: fmt.Errorf("feeling sick"),
n: 1, n: 1,
ips: []net.IP{},
err: fmt.Errorf("feeling sick"), err: fmt.Errorf("feeling sick"),
}, },
"successful": { "successful": {
@@ -66,7 +65,7 @@ func Test_resolveRepeat(t *testing.T) {
} }
ips, err := resolveRepeat( ips, err := resolveRepeat(
context.Background(), lookupIP, host, testCase.n) context.Background(), lookupIP, host, testCase.n, 0)
if testCase.err != nil { if testCase.err != nil {
require.Error(t, err) require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error()) assert.Equal(t, testCase.err.Error(), err.Error())

View File

@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
"time"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network" "github.com/qdm12/golibs/network"
@@ -48,13 +49,24 @@ func findSurfsharkServersFromAPI(ctx context.Context, client network.Client, loo
if err := json.Unmarshal(b, &jsonServers); err != nil { if err := json.Unmarshal(b, &jsonServers); err != nil {
return nil, nil, err return nil, nil, err
} }
hosts := make([]string, len(jsonServers))
for i := range jsonServers {
hosts[i] = jsonServers[i].Host
}
const repetition = 20
const timeBetween = time.Second
const failOnErr = true
hostToIPs, _, err := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
if err != nil {
return nil, nil, err
}
for _, jsonServer := range jsonServers { for _, jsonServer := range jsonServers {
host := jsonServer.Host host := jsonServer.Host
const repetition = 5 IPs := hostToIPs[host]
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition) if len(IPs) == 0 {
if err != nil {
return nil, warnings, err
} else if len(IPs) == 0 {
warning := fmt.Sprintf("no IP address found for host %q", host) warning := fmt.Sprintf("no IP address found for host %q", host)
warnings = append(warnings, warning) warnings = append(warnings, warning)
continue continue
@@ -76,10 +88,8 @@ func findSurfsharkServersFromZip(ctx context.Context, client network.Client, loo
return nil, nil, err return nil, nil, err
} }
mapping := surfsharkSubdomainToRegion() mapping := surfsharkSubdomainToRegion()
hosts := make([]string, 0, len(contents))
for fileName, content := range contents { for fileName, content := range contents {
if err := ctx.Err(); err != nil {
return nil, warnings, err
}
if strings.HasSuffix(fileName, "_tcp.ovpn") { if strings.HasSuffix(fileName, "_tcp.ovpn") {
continue // only parse UDP files continue // only parse UDP files
} }
@@ -92,11 +102,19 @@ func findSurfsharkServersFromZip(ctx context.Context, client network.Client, loo
warnings = append(warnings, err.Error()+" in "+fileName) warnings = append(warnings, err.Error()+" in "+fileName)
continue continue
} }
const repetition = 5 hosts = append(hosts, host)
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition) }
const repetition = 20
const timeBetween = time.Second
const failOnErr = true
hostToIPs, _, err := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
if err != nil { if err != nil {
return nil, warnings, err return nil, warnings, err
} else if len(IPs) == 0 { }
for host, IPs := range hostToIPs {
if len(IPs) == 0 {
warning := fmt.Sprintf("no IP address found for host %q", host) warning := fmt.Sprintf("no IP address found for host %q", host)
warnings = append(warnings, warning) warnings = append(warnings, warning)
continue continue
@@ -118,11 +136,8 @@ func findSurfsharkServersFromZip(ctx context.Context, client network.Client, loo
} }
// process entries in mapping that were not in zip file // process entries in mapping that were not in zip file
remainingServers, newWarnings, err := getRemainingServers(ctx, mapping, lookupIP) remainingServers, newWarnings := getRemainingServers(ctx, mapping, lookupIP)
warnings = append(warnings, newWarnings...) warnings = append(warnings, newWarnings...)
if err != nil {
return nil, warnings, err
}
servers = append(servers, remainingServers...) servers = append(servers, remainingServers...)
sort.Slice(servers, func(i, j int) bool { sort.Slice(servers, func(i, j int) bool {
@@ -132,31 +147,28 @@ func findSurfsharkServersFromZip(ctx context.Context, client network.Client, loo
} }
func getRemainingServers(ctx context.Context, mapping map[string]string, lookupIP lookupIPFunc) ( func getRemainingServers(ctx context.Context, mapping map[string]string, lookupIP lookupIPFunc) (
servers []models.SurfsharkServer, warnings []string, err error) { servers []models.SurfsharkServer, warnings []string) {
for subdomain, region := range mapping { hosts := make([]string, len(mapping))
if err := ctx.Err(); err != nil { i := 0
return servers, warnings, err for subdomain := range mapping {
} hosts[i] = subdomain + ".prod.surfshark.com"
host := subdomain + ".prod.surfshark.com"
const repetition = 3
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition)
if err != nil {
warning := fmt.Sprintf("subdomain %q for region %q from mapping: %s", subdomain, region, err)
warnings = append(warnings, warning)
continue
} else if len(IPs) == 0 {
warning := fmt.Sprintf("subdomain %q for region %q from mapping did not resolve to any IP address",
subdomain, region)
warnings = append(warnings, warning)
continue
} }
const repetition = 20
const timeBetween = time.Second
const failOnErr = false
hostToIPs, warnings, _ := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
for host, IPs := range hostToIPs {
subdomain := strings.TrimSuffix(host, ".prod.surfshark.com")
server := models.SurfsharkServer{ server := models.SurfsharkServer{
Region: region, Region: mapping[subdomain],
IPs: uniqueSortedIPs(IPs), IPs: uniqueSortedIPs(IPs),
} }
servers = append(servers, server) servers = append(servers, server)
} }
return servers, warnings, nil
return servers, warnings
} }
func stringifySurfsharkServers(servers []models.SurfsharkServer) (s string) { func stringifySurfsharkServers(servers []models.SurfsharkServer) (s string) {

View File

@@ -35,6 +35,8 @@ func findVyprvpnServers(ctx context.Context, client network.Client, lookupIP loo
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
hostToRegion := make(map[string]string, len(contents))
for fileName, content := range contents { for fileName, content := range contents {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, warnings, err return nil, warnings, err
@@ -46,15 +48,29 @@ func findVyprvpnServers(ctx context.Context, client network.Client, lookupIP loo
if err != nil { if err != nil {
return nil, warnings, fmt.Errorf("%w in %s", err, fileName) return nil, warnings, fmt.Errorf("%w in %s", err, fileName)
} }
const repetitions = 1 region := strings.TrimSuffix(fileName, ".ovpn")
IPs, err := resolveRepeat(ctx, lookupIP, host, repetitions) region = strings.ReplaceAll(region, " - ", " ")
hostToRegion[host] = region
}
hosts := make([]string, len(hostToRegion))
i := 0
for host := range hostToRegion {
hosts[i] = host
i++
}
const repetition = 1
const timeBetween = 1
const failOnErr = true
hostToIPs, _, err := parallelResolve(ctx, lookupIP, hosts, repetition, timeBetween, failOnErr)
if err != nil { if err != nil {
return nil, warnings, err return nil, warnings, err
} }
region := strings.TrimSuffix(fileName, ".ovpn")
region = strings.ReplaceAll(region, " - ", " ") for host, IPs := range hostToIPs {
server := models.VyprvpnServer{ server := models.VyprvpnServer{
Region: region, Region: hostToRegion[host],
IPs: uniqueSortedIPs(IPs), IPs: uniqueSortedIPs(IPs),
} }
servers = append(servers, server) servers = append(servers, server)