IPv6 hosts check
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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
29
pkg/hosts/hosts.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user