Files
sub2api/backend/internal/pkg/proxyutil/dialer_timeout_test.go
T
li 66ad405dd6 fix(upstream): set explicit TCP dial timeout on upstream transports
http.Transport 未设置 DialContext 时使用零值 net.Dialer(Timeout=0),
DNS 解析与 TCP 握手没有任何上限,只能等内核 TCP 重传耗尽(Linux 约 130 秒)。
ResponseHeaderTimeout 只约束连接建立之后等待响应头的阶段,覆盖不到建连。

上游域名被解析到 443 不可达的 IP 时(DNS 污染 / 路由异常),单个账号就要卡满
内核超时;而多账号故障转移是串行的,一次请求会阻塞数分钟且不写中间错误,
客户端表现为"长时间无首字后直接结束"。

- buildUpstreamTransport 显式设置 DialContext(10s)与 TLSHandshakeTimeout(10s)
- proxyutil SOCKS5 分支的 forward dialer 由 proxy.Direct(零值 dialer,同样无超时)
  换成带 10s 超时的 net.Dialer;该分支会覆盖 Transport.DialContext,
  调用方设置的建连超时对它无效,必须在此补上

Fixes #5151
2026-08-03 19:59:58 +08:00

59 lines
1.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package proxyutil
import (
"context"
"errors"
"net"
"net/http"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/require"
)
var errStub = errors.New("stub dial")
// 回归:SOCKS5 分支覆盖了调用方在 Transport 上设置的 DialContext,
// 底层 forward dialer 必须自带建连超时。proxy.Direct 是零值 net.Dialer,
// 代理不可达时会一直卡到内核 TCP 重传耗尽(Linux 约 130 秒)。
func TestSOCKS5ForwardDialerHasBoundedTimeout(t *testing.T) {
require.Greater(t, socks5ForwardDialer.Timeout, time.Duration(0))
require.Equal(t, socks5DialTimeout, socks5ForwardDialer.Timeout)
require.Equal(t, socks5DialKeepAlive, socks5ForwardDialer.KeepAlive)
}
func TestConfigureTransportProxySOCKS5SetsDialContext(t *testing.T) {
for _, scheme := range []string{"socks5", "socks5h"} {
t.Run(scheme, func(t *testing.T) {
proxyURL, err := url.Parse(scheme + "://127.0.0.1:1080")
require.NoError(t, err)
transport := &http.Transport{}
require.NoError(t, ConfigureTransportProxy(transport, proxyURL))
require.NotNil(t, transport.DialContext)
require.Nil(t, transport.Proxy, "SOCKS5 不应设置 Transport.Proxy")
})
}
}
// HTTP 代理走 Transport.Proxy,不得覆盖调用方设置的 DialContext。
func TestConfigureTransportProxyHTTPPreservesDialContext(t *testing.T) {
proxyURL, err := url.Parse("http://127.0.0.1:8080")
require.NoError(t, err)
called := false
transport := &http.Transport{}
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
called = true
return nil, errStub
}
require.NoError(t, ConfigureTransportProxy(transport, proxyURL))
require.NotNil(t, transport.Proxy)
require.NotNil(t, transport.DialContext)
_, _ = transport.DialContext(context.Background(), "tcp", "127.0.0.1:1")
require.True(t, called, "HTTP 代理分支不应替换调用方的 DialContext")
}