IPv6 hosts check

This commit is contained in:
Ben Sarmiento
2024-01-11 02:28:38 +01:00
parent 72dda4f9fd
commit 9e3d12b008
5 changed files with 156 additions and 92 deletions

View File

@@ -12,6 +12,7 @@ import (
"github.com/debridmediamanager/zurg/internal/handlers" "github.com/debridmediamanager/zurg/internal/handlers"
"github.com/debridmediamanager/zurg/internal/torrent" "github.com/debridmediamanager/zurg/internal/torrent"
"github.com/debridmediamanager/zurg/internal/universal" "github.com/debridmediamanager/zurg/internal/universal"
"github.com/debridmediamanager/zurg/pkg/hosts"
"github.com/debridmediamanager/zurg/pkg/http" "github.com/debridmediamanager/zurg/pkg/http"
"github.com/debridmediamanager/zurg/pkg/logutil" "github.com/debridmediamanager/zurg/pkg/logutil"
"github.com/debridmediamanager/zurg/pkg/premium" "github.com/debridmediamanager/zurg/pkg/premium"
@@ -35,7 +36,7 @@ func MainApp(configPath string) {
os.Exit(1) os.Exit(1)
} }
apiClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), config.GetRealDebridTimeout(), config, log.Named("httpclient")) apiClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), config.GetRealDebridTimeout(), nil, config, log.Named("httpclient"))
rd := realdebrid.NewRealDebrid(apiClient, log.Named("realdebrid")) rd := realdebrid.NewRealDebrid(apiClient, log.Named("realdebrid"))
@@ -51,7 +52,14 @@ func MainApp(configPath string) {
utils.EnsureDirExists("data") // Ensure the data directory exists utils.EnsureDirExists("data") // Ensure the data directory exists
torrentMgr := torrent.NewTorrentManager(config, rd, p, log.Named("manager")) torrentMgr := torrent.NewTorrentManager(config, rd, p, log.Named("manager"))
downloadClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), 0, config, log.Named("dlclient")) var ipv6List []string
if config.ShouldForceIPv6() {
ipv6List, err = hosts.FetchHosts(hosts.IPV6)
if err != nil {
panic(err)
}
}
downloadClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), 0, ipv6List, config, log.Named("dlclient"))
downloader := universal.NewDownloader(downloadClient) downloader := universal.NewDownloader(downloadClient)
router := chi.NewRouter() router := chi.NewRouter()

View File

@@ -16,8 +16,8 @@ func ShowVersion() {
version.GetBuiltAt(), version.GetGitCommit(), version.GetVersion()) version.GetBuiltAt(), version.GetGitCommit(), version.GetVersion())
} }
func NetworkTest(netTestType string) { func NetworkTest(testType string) {
realdebrid.RunTest(netTestType) realdebrid.RunTest(testType)
} }
func ClearDownloads() { func ClearDownloads() {

29
pkg/hosts/hosts.go Normal file
View File

@@ -0,0 +1,29 @@
package hosts
import (
"bufio"
"net/http"
)
const (
IPV4 = "https://gist.githubusercontent.com/yowmamasita/d0c1c7353500d0928cb5242484e8ed06/raw/ipv4.txt"
IPV6 = "https://gist.githubusercontent.com/yowmamasita/d0c1c7353500d0928cb5242484e8ed06/raw/ipv6.txt"
)
func FetchHosts(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var ips []string
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
ips = append(ips, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return ips, nil
}

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"math" "math"
"math/rand"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@@ -22,14 +23,15 @@ const (
) )
type HTTPClient struct { type HTTPClient struct {
client *http.Client client *http.Client
maxRetries int maxRetries int
backoff func(attempt int) time.Duration backoff func(attempt int) time.Duration
getRetryIncr func(resp *http.Response, hasRangeHeader bool, err error) int getRetryIncr func(resp *http.Response, hasRangeHeader bool, err error) int
bearerToken string bearerToken string
cfg config.ConfigInterface restrictToHosts []string
ipv6 cmap.ConcurrentMap[string, string] cfg config.ConfigInterface
log *logutil.Logger ipv6 cmap.ConcurrentMap[string, string]
log *logutil.Logger
} }
// { // {
@@ -46,7 +48,7 @@ func (e *ErrorResponse) Error() string {
return fmt.Sprintf("api response error: %s (code: %d)", e.Message, e.Code) return fmt.Sprintf("api response error: %s (code: %d)", e.Message, e.Code)
} }
func NewHTTPClient(token string, maxRetries int, timeoutSecs int, cfg config.ConfigInterface, log *logutil.Logger) *HTTPClient { func NewHTTPClient(token string, maxRetries int, timeoutSecs int, restrictToHosts []string, cfg config.ConfigInterface, log *logutil.Logger) *HTTPClient {
client := HTTPClient{ client := HTTPClient{
bearerToken: token, bearerToken: token,
client: &http.Client{ client: &http.Client{
@@ -100,9 +102,10 @@ func NewHTTPClient(token string, maxRetries int, timeoutSecs int, cfg config.Con
} }
return RATE_LIMIT_FACTOR return RATE_LIMIT_FACTOR
}, },
cfg: cfg, restrictToHosts: restrictToHosts,
ipv6: cmap.New[string](), cfg: cfg,
log: log, ipv6: cmap.New[string](),
log: log,
} }
if cfg.ShouldForceIPv6() { if cfg.ShouldForceIPv6() {
@@ -115,6 +118,22 @@ func NewHTTPClient(token string, maxRetries int, timeoutSecs int, cfg config.Con
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(restrictToHosts) > 0 {
found := false
for _, h := range restrictToHosts {
if h == host {
found = true
break
}
}
if !found {
log.Warnf("Host %s is not an IPv6 host, replacing with a random host (ensure you have preferred_hosts properly set in your config.yml)", host)
// replace with a random ipv6 host
restrictToHostsLen := len(restrictToHosts)
randomHost := restrictToHosts[rand.Intn(restrictToHostsLen)]
host = randomHost
}
}
ips, err := net.DefaultResolver.LookupIPAddr(ctx, host) ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -1,26 +1,33 @@
package realdebrid package realdebrid
import ( import (
"bufio"
"fmt" "fmt"
"net/http"
"os/exec" "os/exec"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/debridmediamanager/zurg/pkg/hosts"
) )
type IPInfo struct { type HostInfo struct {
Address string Address string
Hops int Hops int
Latency time.Duration Latency time.Duration
} }
func traceroute(ip string) (int, time.Duration, error) { func measureLatency(host, testType string) (int, time.Duration, error) {
traceroutePath := "traceroute"
pingPath := "ping"
if testType == "ipv6" {
traceroutePath = "traceroute6"
pingPath = "ping6"
}
// Try executing traceroute // Try executing traceroute
cmd := exec.Command("traceroute", "-n", "-q", "1", "-w", "1", ip) cmd := exec.Command(traceroutePath, "-n", "-q", "1", "-w", "1", host)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err == nil { if err == nil {
@@ -45,7 +52,7 @@ func traceroute(ip string) (int, time.Duration, error) {
} }
// Traceroute not successful, measure latency using ping // Traceroute not successful, measure latency using ping
pingCmd := exec.Command("ping", "-c", "1", ip) pingCmd := exec.Command(pingPath, "-c", "1", host)
pingOut, pingErr := pingCmd.CombinedOutput() pingOut, pingErr := pingCmd.CombinedOutput()
if pingErr != nil { if pingErr != nil {
@@ -75,110 +82,111 @@ func traceroute(ip string) (int, time.Duration, error) {
return 0, 0, fmt.Errorf("failed to measure latency") return 0, 0, fmt.Errorf("failed to measure latency")
} }
func fetchIPs(url string) ([]string, error) { func RunTest(testType string) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var ips []string
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
ips = append(ips, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return ips, nil
}
func RunTest(netTestType string) {
fmt.Print("Running network test...") fmt.Print("Running network test...")
ipv4URL := "https://gist.githubusercontent.com/yowmamasita/d0c1c7353500d0928cb5242484e8ed06/raw/ipv4.txt" var ipv4Hosts, ipv6Hosts []string
ipv6URL := "https://gist.githubusercontent.com/yowmamasita/d0c1c7353500d0928cb5242484e8ed06/raw/ipv6.txt" var err error
var ips []string if testType == "ipv4" || testType == "both" {
ipv4Hosts, err = hosts.FetchHosts(hosts.IPV4)
if netTestType == "ipv4" || netTestType == "both" {
ipv4IPs, err := fetchIPs(ipv4URL)
if err != nil { if err != nil {
fmt.Println("Error fetching IPv4 IPs:", err) fmt.Println("Error fetching IPv4 hosts:", err)
return return
} }
ips = append(ips, ipv4IPs...)
} }
if netTestType == "ipv6" || netTestType == "both" { if testType == "ipv6" || testType == "both" {
ipv6IPs, err := fetchIPs(ipv6URL) ipv6Hosts, err = hosts.FetchHosts(hosts.IPV6)
if err != nil { if err != nil {
fmt.Println("Error fetching IPv6 IPs:", err) fmt.Println("Error fetching IPv6 hosts:", err)
return return
} }
ips = append(ips, ipv6IPs...) }
var totalHosts int
var hostInfos []HostInfo // Declare the slice to hold IPInfo objects
infoChan := make(chan HostInfo)
if testType == "ipv4" || testType == "both" {
totalHosts += len(ipv4Hosts)
go runLatencyTests(ipv4Hosts, "ipv4", infoChan)
}
if testType == "ipv6" {
totalHosts += len(ipv6Hosts)
go runLatencyTests(ipv6Hosts, "ipv6", infoChan)
}
if testType == "both" {
totalHosts += len(ipv6Hosts)
go runLatencyTests(ipv6Hosts, "ipv4", infoChan)
} }
var wg sync.WaitGroup var wg sync.WaitGroup
infoChan := make(chan IPInfo, len(ips)) wg.Add(1)
semaphore := make(chan struct{}, 10) go func() {
defer wg.Done()
for _, ip := range ips { for i := 0; i < totalHosts; i++ {
wg.Add(1) hostInfo := <-infoChan
semaphore <- struct{}{} hostInfos = append(hostInfos, hostInfo)
go func(ip string) { }
defer wg.Done() close(infoChan)
hops, latency, err := traceroute(ip) }()
if err != nil {
fmt.Printf("Error performing traceroute for %s:%s\n", ip, err)
} else {
infoChan <- IPInfo{Address: ip, Hops: hops, Latency: latency}
}
<-semaphore
}(ip)
}
wg.Wait() wg.Wait()
close(semaphore)
close(infoChan)
fmt.Printf("complete!\n\n")
var ipInfos []IPInfo sort.Slice(hostInfos, func(i, j int) bool {
for info := range infoChan { return hostInfos[i].Latency < hostInfos[j].Latency
ipInfos = append(ipInfos, info)
}
sort.Slice(ipInfos, func(i, j int) bool {
return ipInfos[i].Latency < ipInfos[j].Latency
}) })
const minResults = 10 const minResults = 10
const maxResults = 20 const maxResults = 20
var okIPs []IPInfo var okHosts []HostInfo
if len(ipInfos) > 0 { if len(hostInfos) > 0 {
// Start by adding the best IPs based on hops and latency up to the minResults // Start by adding the best IPs based on hops and latency up to the minResults
for i := 0; i < min(len(ipInfos), minResults); i++ { for i := 0; i < min(len(hostInfos), minResults); i++ {
okIPs = append(okIPs, ipInfos[i]) okHosts = append(okHosts, hostInfos[i])
} }
// Find the highest latency in the current okIPs list // Find the highest latency in the current okHosts list
highestLatency := okIPs[len(okIPs)-1].Latency highestLatency := okHosts[len(okHosts)-1].Latency
// Add any additional IPs that have latency within a reasonable range of the highest latency // Add any additional hosts that have latency within a reasonable range of the highest latency
for _, info := range ipInfos[minResults:] { for _, info := range hostInfos[minResults:] {
if len(okIPs) >= maxResults { if len(okHosts) >= maxResults {
break // Stop adding IPs if maxResults is reached break // Stop adding hosts if maxResults is reached
} }
if info.Latency <= highestLatency+(highestLatency/3) { if info.Latency <= highestLatency+(highestLatency/3) {
okIPs = append(okIPs, info) okHosts = append(okHosts, info)
} }
} }
} }
fmt.Printf("Here are the results, you can copy-paste the following to your config.yml:\n\n") fmt.Printf("Here are the results, you can copy-paste the following to your config.yml:\n\n")
fmt.Println("preferred_hosts:") fmt.Println("preferred_hosts:")
for _, info := range okIPs { for _, info := range okHosts {
fmt.Printf(" - %s # hops: %d latency: %v\n", info.Address, info.Hops, info.Latency) fmt.Printf(" - %s # hops: %d latency: %v\n", info.Address, info.Hops, info.Latency)
} }
} }
func runLatencyTests(hosts []string, testType string, infoChan chan<- HostInfo) {
var wg sync.WaitGroup
semaphore := make(chan struct{}, 10)
for _, host := range hosts {
wg.Add(1)
semaphore <- struct{}{}
go func(host string) {
defer wg.Done()
hops, latency, err := measureLatency(host, testType)
if err != nil {
fmt.Printf("Error measuring latency for %s: %s\n", host, err)
} else {
infoChan <- HostInfo{Address: host, Hops: hops, Latency: latency}
}
<-semaphore
}(host)
}
wg.Wait()
}