Add network test

This commit is contained in:
Ben Adrian Sarmiento
2024-06-16 06:06:57 +02:00
parent b1427c2d63
commit e50806d8e1
4 changed files with 235 additions and 33 deletions

View File

@@ -52,6 +52,17 @@ func MainApp(configPath string) {
os.Exit(1)
}
repo := http.NewIPRepository(log.Named("network_test"))
repoClient := http.NewHTTPClient(
"",
0,
1,
true,
config,
log.Named("network_test"),
)
repo.NetworkTest(repoClient, false)
apiClient := http.NewHTTPClient(
config.GetToken(),
config.GetRetriesUntilFailed(), // default retries = 2
@@ -74,7 +85,7 @@ func MainApp(configPath string) {
"", // no token required for download client
config.GetRetriesUntilFailed(), //
config.GetDownloadTimeoutSecs(), //
true, // download client supports ipv6
true, // set as download client
config,
log.Named("download_client"),
)

View File

@@ -258,9 +258,6 @@ func (t *TorrentManager) applyMediaInfoDetails(torrent *Torrent) {
func (t *TorrentManager) readTorrentFromFile(filePath string) *Torrent {
file, err := os.Open(filePath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return nil
}
defer file.Close()
@@ -318,9 +315,6 @@ func (t *TorrentManager) readInfoFromFile(torrentID string) *realdebrid.TorrentI
filePath := "data/info/" + torrentID + ".zurginfo"
file, err := os.Open(filePath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return nil
}
defer file.Close()

View File

@@ -24,23 +24,18 @@ import (
)
type HTTPClient struct {
client *http.Client
maxRetries int
timeoutSecs int
backoff func(attempt int) time.Duration
bearerToken string
supportIPv6 bool
cfg config.ConfigInterface
ipv6 cmap.ConcurrentMap[string, string]
ipv6Hosts []string
log *logutil.Logger
client *http.Client
maxRetries int
timeoutSecs int
backoff func(attempt int) time.Duration
bearerToken string
isDownloadClient bool
cfg config.ConfigInterface
ipv6 cmap.ConcurrentMap[string, string]
ipv6Hosts []string
log *logutil.Logger
}
// {
// "error": "infringing_file",
// "error_code": 35
// }
type ApiErrorResponse struct {
Message string `json:"error"`
Code int `json:"error_code"`
@@ -54,20 +49,20 @@ func NewHTTPClient(
token string,
maxRetries int,
timeoutSecs int,
supportIPv6 bool,
isDownloadClient bool,
cfg config.ConfigInterface,
log *logutil.Logger,
) *HTTPClient {
client := HTTPClient{
bearerToken: token,
client: &http.Client{},
maxRetries: maxRetries,
timeoutSecs: timeoutSecs,
backoff: backoffFunc,
supportIPv6: supportIPv6,
cfg: cfg,
ipv6: cmap.New[string](),
log: log,
bearerToken: token,
client: &http.Client{},
maxRetries: maxRetries,
timeoutSecs: timeoutSecs,
backoff: backoffFunc,
isDownloadClient: isDownloadClient,
cfg: cfg,
ipv6: cmap.New[string](),
log: log,
}
var dialer proxy.Dialer = &net.Dialer{
@@ -217,7 +212,7 @@ func (r *HTTPClient) Do(req *http.Request) (*http.Response, error) {
func (r *HTTPClient) replaceWithIPv6Host(req *http.Request) {
// don't replace host if IPv6 is not supported or not forced
if !r.supportIPv6 || !r.cfg.ShouldForceIPv6() {
if !r.isDownloadClient || !r.cfg.ShouldForceIPv6() {
return
}
// this host should be replaced

202
pkg/http/ip.go Normal file
View File

@@ -0,0 +1,202 @@
package http
import (
"context"
"encoding/json"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"os"
"time"
"github.com/debridmediamanager/zurg/pkg/logutil"
)
type IPRepository struct {
ipv4 []string
ipv6 []string
latencyMap map[string]float64
log *logutil.Logger
}
func NewIPRepository(log *logutil.Logger) *IPRepository {
repo := &IPRepository{
ipv4: []string{},
ipv6: []string{},
latencyMap: make(map[string]float64),
log: log,
}
repo.lookupDomains()
return repo
}
func (r *IPRepository) NetworkTest(downloadClient *HTTPClient, forceRun bool) {
latencyFile := "data/latency.json"
if !forceRun {
latencyData := r.readLatencyFile(latencyFile)
if latencyData != nil {
r.latencyMap = *latencyData
return
}
}
r.log.Info("Network test will start now. Note that it will only run once and record the latency of each domain for future use.")
r.latencyTest(downloadClient)
r.log.Infof("Network test completed. Saving the results to %s", latencyFile)
r.writeLatencyFile(latencyFile)
}
func (r *IPRepository) lookupDomains() {
limit := 99
increment := 10
start := 0
for {
lastDomainWorked := false
for i := start; i <= limit; i++ {
domain := fmt.Sprintf("%d.download.real-debrid.com", i)
ips, err := net.LookupIP(domain)
if err == nil && len(ips) > 0 {
hasIPv6 := false
for _, ip := range ips {
if ip.To4() == nil {
hasIPv6 = true
}
}
// assume it always has ipv4
r.ipv4 = append(r.ipv4, domain)
if hasIPv6 {
r.ipv6 = append(r.ipv6, domain)
}
if i == limit {
lastDomainWorked = true
}
}
domain2 := fmt.Sprintf("%d.download.real-debrid.cloud", i)
ips2, err := net.LookupIP(domain2)
if err == nil && len(ips2) > 0 {
hasIPv6 := false
for _, ip := range ips {
if ip.To4() == nil {
hasIPv6 = true
}
}
r.ipv4 = append(r.ipv4, domain2)
if hasIPv6 {
r.ipv6 = append(r.ipv6, domain2)
}
if i == limit {
lastDomainWorked = true
}
}
}
if lastDomainWorked {
start = limit + 1
limit += increment
} else {
break
}
}
r.log.Infof("Found %d IPv4 domains and %d IPv6 domains", len(r.ipv4), len(r.ipv6))
}
func (r *IPRepository) latencyTest(downloadClient *HTTPClient) {
const testFileSize = 1
const iterations = 3
for _, domain := range r.ipv4 {
url := fmt.Sprintf("https://%s/speedtest/test.rar/%f", domain, rand.Float64())
var totalDuration float64
hasError := false
for i := 0; i < iterations; i++ {
ctx, cancel := context.WithTimeout(context.Background(), iterations*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
r.log.Warnf("Failed to create request for %s: %v", domain, err)
hasError = true
break
}
headers := make(http.Header)
headers.Set("Range", fmt.Sprintf("bytes=0-%d", testFileSize-1))
req.Header = headers
start := time.Now()
resp, err := downloadClient.Do(req)
if err != nil {
r.log.Warnf("Failed to download from %s: %v", domain, err)
hasError = true
break
}
limitedReader := io.LimitReader(resp.Body, testFileSize)
_, err = io.Copy(io.Discard, limitedReader)
resp.Body.Close()
if err != nil && err != io.EOF {
r.log.Warnf("Failed to read from %s: %v", domain, err)
hasError = true
break
}
duration := time.Since(start).Seconds()
totalDuration += duration
}
if hasError {
continue
}
r.latencyMap[domain] = totalDuration / 3
r.log.Debugf("Latency from %s: %.5f seconds", domain, r.latencyMap[domain])
}
}
func (r *IPRepository) readLatencyFile(latencyFile string) *map[string]float64 {
if _, err := os.Stat(latencyFile); err == nil {
file, err := os.Open(latencyFile)
if err != nil {
return nil
}
defer file.Close()
jsonData, err := io.ReadAll(file)
if err != nil {
return nil
}
var latencyMap map[string]float64
if err := json.Unmarshal(jsonData, &latencyMap); err != nil {
return nil
}
return &latencyMap
}
return nil
}
func (r *IPRepository) writeLatencyFile(latencyFile string) {
file, err := os.Create(latencyFile)
if err != nil {
r.log.Warnf("Cannot create latency file %s: %v", latencyFile, err)
return
}
defer file.Close()
jsonData, err := json.Marshal(r.latencyMap)
if err != nil {
r.log.Warnf("Cannot marshal latency map: %v", err)
return
}
if _, err := file.Write(jsonData); err != nil {
r.log.Warnf("Cannot write to latency file %s: %v", latencyFile, err)
return
}
}