From d586793169a7290839400d7fee49cbab60e3d0e4 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Fri, 20 Feb 2026 16:40:51 +0000 Subject: [PATCH] fix(all): increase global http client timeout to 35s and precise lower timeouts where needed - Fix DNS blocklists slow downloads, fix #3102 - Leave 35s timeout for updaters - Set timeouts to 1s for local calls - Set timeouts to 5s for LAN VPN calls and small external calls - Set timeouts to 10s external VPN API calls --- cmd/gluetun/main.go | 2 +- internal/healthcheck/client.go | 4 ++++ .../privateinternetaccess/portforward.go | 17 +++++++++++++++++ internal/provider/privatevpn/portforward.go | 7 +++++++ internal/provider/protonvpn/updater/version.go | 7 +++++++ internal/publicip/api/cloudflare.go | 7 +++++++ internal/publicip/api/echoip.go | 7 +++++++ internal/publicip/api/ip2location.go | 7 +++++++ internal/publicip/api/ipinfo.go | 7 +++++++ internal/version/github.go | 12 ++++++++++++ 10 files changed, 76 insertions(+), 1 deletion(-) diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index 206294a8..dad17385 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -264,7 +264,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID) - const clientTimeout = 15 * time.Second + const clientTimeout = 35 * time.Second httpClient := &http.Client{Timeout: clientTimeout} // Create configurators alpineConf := alpine.New() diff --git a/internal/healthcheck/client.go b/internal/healthcheck/client.go index 7675a4f3..d52a58fd 100644 --- a/internal/healthcheck/client.go +++ b/internal/healthcheck/client.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "time" ) var ErrHTTPStatusNotOK = errors.New("HTTP response status is not OK") @@ -21,6 +22,9 @@ func NewClient(httpClient *http.Client) *Client { } func (c *Client) Check(ctx context.Context, url string) error { + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return err diff --git a/internal/provider/privateinternetaccess/portforward.go b/internal/provider/privateinternetaccess/portforward.go index 994b008f..ad0912ec 100644 --- a/internal/provider/privateinternetaccess/portforward.go +++ b/internal/provider/privateinternetaccess/portforward.go @@ -260,6 +260,12 @@ func fetchToken(ctx context.Context, client *http.Client, url.QueryEscape(password): "", } + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 10 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + form := url.Values{} form.Add("username", username) form.Add("password", password) @@ -304,6 +310,11 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, apiIP netip. ) { errSubstitutions := map[string]string{url.QueryEscape(token): ""} + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() queryParams := make(url.Values) queryParams.Add("token", token) url := url.URL{ @@ -353,6 +364,12 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, apiIP netip. var ErrBadResponse = errors.New("bad response received") func bindPort(ctx context.Context, client *http.Client, apiIPAddress netip.Addr, data piaPortForwardData) (err error) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + payload, err := packPayload(data.Port, data.Token, data.Expiration) if err != nil { return fmt.Errorf("serializing payload: %w", err) diff --git a/internal/provider/privatevpn/portforward.go b/internal/provider/privatevpn/portforward.go index 2e02da28..f68c1d6a 100644 --- a/internal/provider/privatevpn/portforward.go +++ b/internal/provider/privatevpn/portforward.go @@ -9,6 +9,7 @@ import ( "net/http" "regexp" "strconv" + "time" "github.com/qdm12/gluetun/internal/provider/common" "github.com/qdm12/gluetun/internal/provider/utils" @@ -23,6 +24,12 @@ var ErrPortForwardedNotFound = errors.New("port forwarded not found") func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObjects) ( ports []uint16, err error, ) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 10 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + url := "https://connect.pvdatanet.com/v3/Api/port?ip[]=" + objects.InternalIP.String() request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { diff --git a/internal/provider/protonvpn/updater/version.go b/internal/provider/protonvpn/updater/version.go index fe64cdba..bdf0587f 100644 --- a/internal/provider/protonvpn/updater/version.go +++ b/internal/provider/protonvpn/updater/version.go @@ -8,6 +8,7 @@ import ( "net/http" "regexp" "strings" + "time" ) // getMostRecentStableTag finds the most recent proton-account stable tag version, @@ -18,6 +19,12 @@ func getMostRecentStableTag(ctx context.Context, client *http.Client) (version s page := 1 regexVersion := regexp.MustCompile(`^proton-account@(\d+\.\d+\.\d+\.\d+)$`) for ctx.Err() == nil { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + url := "https://api.github.com/repos/ProtonMail/WebClients/tags?per_page=30&page=" + fmt.Sprint(page) request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) diff --git a/internal/publicip/api/cloudflare.go b/internal/publicip/api/cloudflare.go index 06ae55a3..e9e7b3cc 100644 --- a/internal/publicip/api/cloudflare.go +++ b/internal/publicip/api/cloudflare.go @@ -7,6 +7,7 @@ import ( "net/http" "net/netip" "strings" + "time" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" @@ -40,6 +41,12 @@ func (c *cloudflare) Token() (token string) { func (c *cloudflare) FetchInfo(ctx context.Context, ip netip.Addr) ( result models.PublicIP, err error, ) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + urlBase := "https://speed.cloudflare.com" url := urlBase + "/meta" if ip.IsValid() { diff --git a/internal/publicip/api/echoip.go b/internal/publicip/api/echoip.go index 07351953..bad3ed75 100644 --- a/internal/publicip/api/echoip.go +++ b/internal/publicip/api/echoip.go @@ -7,6 +7,7 @@ import ( "net/http" "net/netip" "strings" + "time" "github.com/qdm12/gluetun/internal/models" ) @@ -44,6 +45,12 @@ func (e *echoip) Token() string { func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) ( result models.PublicIP, err error, ) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + url := e.url + "/json" if ip.IsValid() { url += "?ip=" + ip.String() diff --git a/internal/publicip/api/ip2location.go b/internal/publicip/api/ip2location.go index c24ee58a..58b50102 100644 --- a/internal/publicip/api/ip2location.go +++ b/internal/publicip/api/ip2location.go @@ -7,6 +7,7 @@ import ( "net/http" "net/netip" "strings" + "time" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" @@ -44,6 +45,12 @@ func (i *ip2Location) Token() string { func (i *ip2Location) FetchInfo(ctx context.Context, ip netip.Addr) ( result models.PublicIP, err error, ) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + url := "https://api.ip2location.io/" if ip.IsValid() { url += "?ip=" + ip.String() diff --git a/internal/publicip/api/ipinfo.go b/internal/publicip/api/ipinfo.go index 300ec9c2..89ba7d59 100644 --- a/internal/publicip/api/ipinfo.go +++ b/internal/publicip/api/ipinfo.go @@ -7,6 +7,7 @@ import ( "net/http" "net/netip" "strings" + "time" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" @@ -42,6 +43,12 @@ func (i *ipInfo) Token() string { func (i *ipInfo) FetchInfo(ctx context.Context, ip netip.Addr) ( result models.PublicIP, err error, ) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + url := "https://ipinfo.io/" switch { case ip.Is6(): diff --git a/internal/version/github.go b/internal/version/github.go index a8fce85c..1ab6fb67 100644 --- a/internal/version/github.go +++ b/internal/version/github.go @@ -28,6 +28,12 @@ type githubCommit struct { var errHTTPStatusCode = errors.New("bad response HTTP status code") func getGithubReleases(ctx context.Context, client *http.Client) (releases []githubRelease, err error) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + const url = "https://api.github.com/repos/qdm12/gluetun/releases" request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -53,6 +59,12 @@ func getGithubReleases(ctx context.Context, client *http.Client) (releases []git } func getGithubCommits(ctx context.Context, client *http.Client) (commits []githubCommit, err error) { + // Define a timeout since the default client has a large timeout and we don't + // want to wait too long. + const timeout = 5 * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + const url = "https://api.github.com/repos/qdm12/gluetun/commits" request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil {