mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +03:00
fix(net): stop per-request clients parking connections to remote hosts
The node certificate probe builds a transport for one request and dropped it with the connection still parked, and the remote routing client kept idle connections with no timeout; either stayed open for as long as the remote side wanted. Close the probe's idle connections when it returns, and turn keep-alive off for routing downloads, which are minutes apart. The routing client dials only public hosts, so no loopback test reaches it; TestFetchCertFingerprintLeavesNoIdleConnection covers the probe.
This commit is contained in:
@@ -486,7 +486,9 @@ func newRemoteRoutingHTTPClient() *http.Client {
|
|||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
TLSHandshakeTimeout: 4 * time.Second,
|
TLSHandshakeTimeout: 4 * time.Second,
|
||||||
ResponseHeaderTimeout: 5 * time.Second,
|
ResponseHeaderTimeout: 5 * time.Second,
|
||||||
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
|
// Downloads are minutes apart, and a parked connection lives as long as the host likes.
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
|
||||||
}
|
}
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Timeout: remoteRoutingHTTPTimeout,
|
Timeout: remoteRoutingHTTPTimeout,
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ func (s *NodeService) FetchCertFingerprint(ctx context.Context, n *model.Node) (
|
|||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // lgtm[go/disabled-certificate-check]
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // lgtm[go/disabled-certificate-check]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
|
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
|
||||||
@@ -250,3 +258,33 @@ func TestFilterNodeSnapshotKeepsAdoptedAlias(t *testing.T) {
|
|||||||
t.Fatalf("filtered snapshot = %#v, want adopted alias only", snap.Inbounds)
|
t.Fatalf("filtered snapshot = %#v, want adopted alias only", snap.Inbounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The probe builds a transport per call; what it parks would stay open for as long
|
||||||
|
// as the node, an untrusted peer, keeps the connection.
|
||||||
|
func TestFetchCertFingerprintLeavesNoIdleConnection(t *testing.T) {
|
||||||
|
var open atomic.Int32
|
||||||
|
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
server.Config.ConnState = func(_ net.Conn, state http.ConnState) {
|
||||||
|
switch state {
|
||||||
|
case http.StateNew:
|
||||||
|
open.Add(1)
|
||||||
|
case http.StateClosed, http.StateHijacked:
|
||||||
|
open.Add(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.StartTLS()
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
u, _ := url.Parse(server.URL)
|
||||||
|
port, _ := strconv.Atoi(u.Port())
|
||||||
|
node := &model.Node{Scheme: "https", Address: u.Hostname(), Port: port, AllowPrivateAddress: true}
|
||||||
|
if _, err := (&NodeService{}).FetchCertFingerprint(context.Background(), node); err != nil {
|
||||||
|
t.Fatalf("FetchCertFingerprint: %v", err)
|
||||||
|
}
|
||||||
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
for open.Load() != 0 && time.Now().Before(deadline) {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if n := open.Load(); n != 0 {
|
||||||
|
t.Fatalf("%d connection(s) to the node still open after the probe", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user