Fix surfshark updater and update server data
This commit is contained in:
@@ -7,15 +7,18 @@ import (
|
||||
)
|
||||
|
||||
func uniqueSortedIPs(ips []net.IP) []net.IP {
|
||||
uniqueIPs := make(map[string]struct{})
|
||||
uniqueIPs := make(map[string]struct{}, len(ips))
|
||||
for _, ip := range ips {
|
||||
uniqueIPs[ip.String()] = struct{}{}
|
||||
key := ip.String()
|
||||
uniqueIPs[key] = struct{}{}
|
||||
}
|
||||
ips = make([]net.IP, len(uniqueIPs))
|
||||
i := 0
|
||||
for ip := range uniqueIPs {
|
||||
ips[i] = net.ParseIP(ip)
|
||||
i++
|
||||
ips = make([]net.IP, 0, len(uniqueIPs))
|
||||
for key := range uniqueIPs {
|
||||
ip := net.ParseIP(key)
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
sort.Slice(ips, func(i, j int) bool {
|
||||
return bytes.Compare(ips[i], ips[j]) < 0
|
||||
|
||||
41
internal/updater/ips_test.go
Normal file
41
internal/updater/ips_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_uniqueSortedIPs(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
inputIPs []net.IP
|
||||
outputIPs []net.IP
|
||||
}{
|
||||
"nil": {
|
||||
inputIPs: nil,
|
||||
outputIPs: []net.IP{},
|
||||
},
|
||||
"empty": {
|
||||
inputIPs: []net.IP{},
|
||||
outputIPs: []net.IP{},
|
||||
},
|
||||
"single IPv4": {
|
||||
inputIPs: []net.IP{{1, 1, 1, 1}},
|
||||
outputIPs: []net.IP{{1, 1, 1, 1}},
|
||||
},
|
||||
"two IPv4s": {
|
||||
inputIPs: []net.IP{{1, 1, 2, 1}, {1, 1, 1, 1}},
|
||||
outputIPs: []net.IP{{1, 1, 1, 1}, {1, 1, 2, 1}},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
outputIPs := uniqueSortedIPs(testCase.inputIPs)
|
||||
assert.Equal(t, testCase.outputIPs, outputIPs)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,12 @@ import (
|
||||
)
|
||||
|
||||
func (u *updater) updateSurfshark(ctx context.Context) (err error) {
|
||||
servers, err := findSurfsharkServers(ctx, u.lookupIP)
|
||||
servers, warnings, err := findSurfsharkServers(ctx, u.lookupIP)
|
||||
if u.options.CLI {
|
||||
for _, warning := range warnings {
|
||||
u.logger.Warn("Surfshark: %s", warning)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Surfshark servers: %w", err)
|
||||
}
|
||||
@@ -23,51 +28,54 @@ func (u *updater) updateSurfshark(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func findSurfsharkServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.SurfsharkServer, err error) {
|
||||
const zipURL = "https://account.surfshark.com/api/v1/server/configurations"
|
||||
func findSurfsharkServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.SurfsharkServer, warnings []string, err error) {
|
||||
const zipURL = "https://my.surfshark.com/vpn/api/v1/server/configurations"
|
||||
contents, err := fetchAndExtractFiles(zipURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
mapping := surfsharkSubdomainToRegion()
|
||||
for fileName, content := range contents {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
return nil, warnings, err
|
||||
}
|
||||
if strings.HasSuffix(fileName, "_tcp.ovpn") {
|
||||
continue // only parse UDP files
|
||||
}
|
||||
remoteLines := extractRemoteLinesFromOpenvpn(content)
|
||||
if len(remoteLines) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any remote lines in %s", fileName)
|
||||
return nil, warnings, fmt.Errorf("cannot find any remote lines in %s", fileName)
|
||||
}
|
||||
hosts := extractHostnamesFromRemoteLines(remoteLines)
|
||||
if len(hosts) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any hosts in %s", fileName)
|
||||
return nil, warnings, fmt.Errorf("cannot find any hosts in %s", fileName)
|
||||
} else if len(hosts) > 1 {
|
||||
warning := fmt.Sprintf("more than one host in %q, only taking first one %q into account", fileName, hosts[0])
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
var IPs []net.IP
|
||||
var region string
|
||||
for _, host := range hosts {
|
||||
if net.ParseIP(host) != nil {
|
||||
// only a few IP addresses, no idea for what region
|
||||
// ignore them
|
||||
continue
|
||||
}
|
||||
const repetition = 3
|
||||
newIPs, err := resolveRepeat(ctx, lookupIP, host, repetition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
IPs = append(IPs, newIPs...)
|
||||
if region == "" {
|
||||
subdomain := strings.TrimSuffix(host, ".prod.surfshark.com")
|
||||
region = surfsharkSubdomainToRegion(subdomain)
|
||||
}
|
||||
host := hosts[0]
|
||||
if net.ParseIP(host) != nil {
|
||||
warning := fmt.Sprintf("ignoring IP address host %q in %q", host, fileName)
|
||||
warnings = append(warnings, warning)
|
||||
continue
|
||||
}
|
||||
if len(IPs) == 0 {
|
||||
continue // only IPs, no hostnames found
|
||||
const repetition = 5
|
||||
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
} else if len(IPs) == 0 {
|
||||
warning := fmt.Sprintf("no IP address found for host %q", host)
|
||||
warnings = append(warnings, warning)
|
||||
continue
|
||||
}
|
||||
if region == "" { // region not found in mapping
|
||||
subdomain := strings.TrimSuffix(host, ".prod.surfshark.com")
|
||||
region, ok := mapping[subdomain]
|
||||
if ok {
|
||||
delete(mapping, subdomain)
|
||||
} else {
|
||||
region = strings.TrimSuffix(hosts[0], ".prod.surfshark.com")
|
||||
warning := fmt.Sprintf("subdomain %q not found in Surfshark mapping", subdomain)
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
server := models.SurfsharkServer{
|
||||
Region: region,
|
||||
@@ -75,10 +83,45 @@ func findSurfsharkServers(ctx context.Context, lookupIP lookupIPFunc) (servers [
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
// process entries in mapping that were not in zip file
|
||||
remainingServers, newWarnings, err := getRemainingServers(ctx, mapping, lookupIP)
|
||||
warnings = append(warnings, newWarnings...)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
servers = append(servers, remainingServers...)
|
||||
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Region < servers[j].Region
|
||||
})
|
||||
return servers, nil
|
||||
return servers, warnings, nil
|
||||
}
|
||||
func getRemainingServers(ctx context.Context, mapping map[string]string, lookupIP lookupIPFunc) (
|
||||
servers []models.SurfsharkServer, warnings []string, err error) {
|
||||
for subdomain, region := range mapping {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return servers, warnings, err
|
||||
}
|
||||
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
|
||||
}
|
||||
server := models.SurfsharkServer{
|
||||
Region: region,
|
||||
IPs: uniqueSortedIPs(IPs),
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
func stringifySurfsharkServers(servers []models.SurfsharkServer) (s string) {
|
||||
@@ -92,7 +135,7 @@ func stringifySurfsharkServers(servers []models.SurfsharkServer) (s string) {
|
||||
return s
|
||||
}
|
||||
|
||||
func surfsharkSubdomainToRegion(subdomain string) (region string) {
|
||||
func surfsharkSubdomainToRegion() (mapping map[string]string) {
|
||||
return map[string]string{
|
||||
"ae-dub": "United Arab Emirates",
|
||||
"al-tia": "Albania",
|
||||
@@ -248,5 +291,9 @@ func surfsharkSubdomainToRegion(subdomain string) (region string) {
|
||||
"ar-bua": "Argentina Buenos Aires",
|
||||
"tr-ist": "Turkey Istanbul",
|
||||
"mx-mex": "Mexico City Mexico",
|
||||
}[subdomain]
|
||||
"ca-tor-mp001": "Canada Toronto mp001",
|
||||
"de-fra-mp001": "Germany Frankfurt mp001",
|
||||
"nl-ams-mp001": "Netherlands Amsterdam mp001",
|
||||
"us-sfo-mp001": "US San Francisco mp001",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user