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 <andrea@gottardo.me>
pull/13564/head
Andrea Gottardo 2 months ago committed by GitHub
parent e66fe1f2e8
commit ed1ac799c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 // 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. // 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) { 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) req, err := http.NewRequestWithContext(ctx, "GET", e.URL.String(), nil)
if err != nil { if err != nil {
return false, err return false, err
@ -213,7 +216,8 @@ func (d *Detector) dialContext(ctx context.Context, network, addr string) (net.C
ifIndex := d.currIfIndex ifIndex := d.currIfIndex
dl := net.Dialer{ dl := &net.Dialer{
Timeout: Timeout,
Control: func(network, address string, c syscall.RawConn) error { Control: func(network, address string, c syscall.RawConn) error {
return setSocketInterfaceIndex(c, ifIndex, d.logf) return setSocketInterfaceIndex(c, ifIndex, d.logf)
}, },

Loading…
Cancel
Save