diff --git a/bypass/bypass.go b/bypass/bypass.go index 0ad66e94..938fe6fb 100644 --- a/bypass/bypass.go +++ b/bypass/bypass.go @@ -24,15 +24,25 @@ const ( // connectTimeout is the default timeout for the HTTP CONNECT handshake // when the caller's context has no deadline. connectTimeout = 10 * time.Second + + // dialTimeout is the timeout for establishing the initial TCP connection. + dialTimeout = 30 * time.Second + + // dialKeepAlive is the interval for TCP keep-alive probes. + dialKeepAlive = 30 * time.Second ) // DialContext tries to connect through the local bypass proxy. If the proxy is // not reachable (VPN not running), it falls back to a direct dial. func DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - proxyConn, err := (&net.Dialer{}).DialContext(ctx, "tcp", ProxyAddr) + dialer := &net.Dialer{ + Timeout: dialTimeout, + KeepAlive: dialKeepAlive, + } + proxyConn, err := dialer.DialContext(ctx, "tcp", ProxyAddr) if err != nil { slog.Debug("bypass proxy not reachable, falling back to direct dial", "addr", addr, "error", err) - return (&net.Dialer{}).DialContext(ctx, network, addr) + return dialer.DialContext(ctx, network, addr) } tunnelConn, err := httpConnect(ctx, proxyConn, addr) if err != nil { diff --git a/go.mod b/go.mod index 59b61ab7..3f59a414 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/getlantern/appdir v0.0.0-20250324200952-507a0625eb01 github.com/getlantern/common v1.2.1-0.20260121160752-d8ee5791108f github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d - github.com/getlantern/fronted v0.0.0-20260219001615-7eabaa834efe + github.com/getlantern/fronted v0.0.0-20260225205111-41c9e534027a github.com/getlantern/keepcurrent v0.0.0-20240126172110-2e0264ca385d github.com/getlantern/kindling v0.0.0-20260219202502-df15c15dc5fb github.com/getlantern/lantern-box v0.0.6-0.20260220213333-4b20583e43ff diff --git a/go.sum b/go.sum index 84fed3f1..c6bbc249 100644 --- a/go.sum +++ b/go.sum @@ -206,6 +206,8 @@ github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731 h1:v+vJ3LgV4nW4 github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731/go.mod h1:XZwE+iIlAgr64OFbXKFNCllBwV4wEipPx8Hlo2gZdbM= github.com/getlantern/fronted v0.0.0-20260219001615-7eabaa834efe h1:Q4fwCDDqgw21GGitBPXol68wbAJGBNBolNsa8MS5wXk= github.com/getlantern/fronted v0.0.0-20260219001615-7eabaa834efe/go.mod h1:1a+iv1xzGxZWj/vCHzr8Z3dF9H1sNTuMSPHUqRsgbl0= +github.com/getlantern/fronted v0.0.0-20260225205111-41c9e534027a h1:mZVn1e2boHzKk4JgKwQ4Eqhn+omowFWzPduxGHCmYRs= +github.com/getlantern/fronted v0.0.0-20260225205111-41c9e534027a/go.mod h1:1a+iv1xzGxZWj/vCHzr8Z3dF9H1sNTuMSPHUqRsgbl0= github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA= github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 h1:NlQedYmPI3pRAXJb+hLVVDGqfvvXGRPV8vp7XOjKAZ0= github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU= diff --git a/kindling/fronted/fronted.go b/kindling/fronted/fronted.go index 49a26257..92e1f71d 100644 --- a/kindling/fronted/fronted.go +++ b/kindling/fronted/fronted.go @@ -6,6 +6,7 @@ import ( "log/slog" "github.com/getlantern/fronted" + "github.com/getlantern/radiance/bypass" "github.com/getlantern/radiance/kindling/smart" "go.opentelemetry.io/otel" ) @@ -24,6 +25,7 @@ func NewFronted(ctx context.Context, panicListener func(string), cacheFile strin frontedOptions := []fronted.Option{ fronted.WithPanicListener(panicListener), fronted.WithCacheFile(cacheFile), + fronted.WithDialer(bypass.DialContext), } httpClient, err := smart.NewHTTPClientWithSmartTransport(logWriter, configURL) if err != nil { diff --git a/servers/manager.go b/servers/manager.go index 9a101aed..e1a43c4b 100644 --- a/servers/manager.go +++ b/servers/manager.go @@ -26,6 +26,7 @@ import ( C "github.com/getlantern/common" + "github.com/getlantern/radiance/bypass" "github.com/getlantern/radiance/common" "github.com/getlantern/radiance/common/atomicfile" "github.com/getlantern/radiance/internal" @@ -101,9 +102,8 @@ func NewManager(dataPath string) (*Manager, error) { serversFile: filepath.Join(dataPath, common.ServersFileName), access: sync.RWMutex{}, - // Note that we use a regular http.Client here because it is only used to access private - // servers the user has created. - // Use the same configuration as http.DefaultClient. + // Use the bypass proxy dialer to route requests outside the VPN tunnel. + // This client is only used to access private servers the user has created. httpClient: retryableHTTPClient().StandardClient(), } @@ -118,11 +118,8 @@ func NewManager(dataPath string) (*Manager, error) { func retryableHTTPClient() *retryablehttp.Client { transport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).DialContext, + Proxy: http.ProxyFromEnvironment, + DialContext: bypass.DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second,