From ed1ac799c80cb30111997224d43acdde2492f6d6 Mon Sep 17 00:00:00 2001 From: Andrea Gottardo Date: Wed, 2 Oct 2024 08:29:46 -0700 Subject: [PATCH] net/captivedetection: set Timeout on net.Dialer (#13613) Updates tailscale/tailscale#1634 Updates tailscale/tailscale#13265 Captive portal detection uses a custom `net.Dialer` in its `http.Client`. This custom Dialer ensures that the socket is bound specifically to the Wi-Fi interface. This is crucial because without it, if any default routes are set, the outgoing requests for detecting a captive portal would bypass Wi-Fi and go through the default route instead. The Dialer did not have a Timeout property configured, so the default system timeout was applied. This caused issues in #13265, where we attempted to make captive portal detection requests over an IPsec interface used for Wi-Fi Calling. The call to `connect()` would fail and remain blocked until the system timeout (approximately 1 minute) was reached. In #13598, I simply excluded the IPsec interface from captive portal detection. This was a quick and safe mitigation for the issue. This PR is a follow-up to make the process more robust, by setting a 3 seconds timeout on any connection establishment on any interface (this is the same timeout interval we were already setting on the HTTP client). Signed-off-by: Andrea Gottardo --- net/captivedetection/captivedetection.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/captivedetection/captivedetection.go b/net/captivedetection/captivedetection.go index c99fdd44f..c6e8bca3a 100644 --- a/net/captivedetection/captivedetection.go +++ b/net/captivedetection/captivedetection.go @@ -179,6 +179,9 @@ func (d *Detector) detectOnInterface(ctx context.Context, ifIndex int, endpoints // verifyCaptivePortalEndpoint checks if the given Endpoint is a captive portal by making an HTTP request to the // given Endpoint URL using the interface with index ifIndex, and checking if the response looks like a captive portal. func (d *Detector) verifyCaptivePortalEndpoint(ctx context.Context, e Endpoint, ifIndex int) (found bool, err error) { + ctx, cancel := context.WithTimeout(ctx, Timeout) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "GET", e.URL.String(), nil) if err != nil { return false, err @@ -213,7 +216,8 @@ func (d *Detector) dialContext(ctx context.Context, network, addr string) (net.C ifIndex := d.currIfIndex - dl := net.Dialer{ + dl := &net.Dialer{ + Timeout: Timeout, Control: func(network, address string, c syscall.RawConn) error { return setSocketInterfaceIndex(c, ifIndex, d.logf) },