package cli import ( "fmt" "net" "net/http" "time" ) // AppCallback provides hooks for injecting certain functionalities // from mobile platforms to main ctrld cli. // This allows mobile applications to customize behavior without modifying core CLI code type AppCallback struct { HostName func() string LanIp func() string MacAddress func() string Exit func(error string) } // AppConfig allows overwriting ctrld cli flags from mobile platforms. // This provides a clean interface for mobile apps to configure ctrld behavior type AppConfig struct { CdUID string ProvisionID string CustomHostname string HomeDir string UpstreamProto string Verbose int LogPath string } // Network and HTTP configuration constants const ( // defaultHTTPTimeout provides reasonable timeout for HTTP operations // This prevents hanging requests while allowing sufficient time for network delays defaultHTTPTimeout = 30 * time.Second // defaultMaxRetries provides retry attempts for failed HTTP requests // This improves reliability in unstable network conditions defaultMaxRetries = 3 // downloadServerIp is the fallback IP for download operations // This ensures downloads work even when DNS resolution fails downloadServerIp = "23.171.240.151" ) // httpClientWithFallback returns an HTTP client configured with timeout and IPv4 fallback // This ensures reliable HTTP operations by preferring IPv4 and handling timeouts gracefully func httpClientWithFallback(timeout time.Duration) *http.Client { return &http.Client{ Timeout: timeout, Transport: &http.Transport{ // Prefer IPv4 over IPv6 // This improves compatibility with networks that have IPv6 issues DialContext: (&net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 30 * time.Second, FallbackDelay: 1 * time.Millisecond, // Very small delay to prefer IPv4 }).DialContext, }, } } // doWithRetry performs an HTTP request with retries // This improves reliability by automatically retrying failed requests with exponential backoff func doWithRetry(req *http.Request, maxRetries int, ip string) (*http.Response, error) { var lastErr error client := httpClientWithFallback(defaultHTTPTimeout) var ipReq *http.Request if ip != "" { ipReq = req.Clone(req.Context()) ipReq.Host = ip ipReq.URL.Host = ip } for attempt := 0; attempt < maxRetries; attempt++ { if attempt > 0 { // Linear backoff reduces server load and improves success rate time.Sleep(time.Second * time.Duration(attempt+1)) } resp, err := client.Do(req) if err == nil { return resp, nil } if ipReq != nil { mainLog.Load().Warn().Err(err).Msgf("Dial to %q failed", req.Host) mainLog.Load().Warn().Msgf("Fallback to direct ip to download prod version: %q", ip) resp, err = client.Do(ipReq) if err == nil { return resp, nil } } lastErr = err mainLog.Load().Debug().Err(err). Str("method", req.Method). Str("url", req.URL.String()). Msgf("HTTP request attempt %d/%d failed", attempt+1, maxRetries) } return nil, fmt.Errorf("failed after %d attempts to %s %s: %v", maxRetries, req.Method, req.URL, lastErr) } // Helper for making GET requests with retries // This provides a simplified interface for common GET operations with built-in retry logic func getWithRetry(url string, ip string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } return doWithRetry(req, defaultMaxRetries, ip) }