mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-26 21:17:14 +00:00
feat(outbound): add real-delay connection test mode
The HTTP probe reports the warm per-request round-trip, which reads lower than the delay figure client apps show for the same server. Add a third "real" test mode that reuses the temp-instance HTTP probe but reports the cold request's full elapsed time - tunnel establishment included - and skips the warm request. UDP-transport outbounds forced out of the TCP lane still report "http"; in real mode they report "real". The mode joins the TCP/HTTP toggle on the outbounds tab, with the label translated in all 13 locales.
This commit is contained in:
@@ -464,6 +464,94 @@ func TestTestOutboundsHTTPBatchThroughStubSocks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestOutboundsRealDelayBatchThroughStubSocks(t *testing.T) {
|
||||
var mu sync.Mutex
|
||||
requestsPerConn := make(map[string]int)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
mu.Lock()
|
||||
requestsPerConn[r.RemoteAddr]++
|
||||
mu.Unlock()
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
withStubProcess(t, func(cfg *xray.Config, configPath string) batchProcess {
|
||||
return &stubProcess{cfg: cfg, serveSocks: true}
|
||||
})
|
||||
|
||||
batch := mustJSON(t, []any{
|
||||
map[string]any{"tag": "a", "protocol": "vless"},
|
||||
map[string]any{"tag": "wg", "protocol": "wireguard"},
|
||||
})
|
||||
results, err := (&OutboundService{}).TestOutbounds(batch, srv.URL, "", "real")
|
||||
if err != nil {
|
||||
t.Fatalf("TestOutbounds: %v", err)
|
||||
}
|
||||
for i, r := range results {
|
||||
if !r.Success {
|
||||
t.Fatalf("result %d failed: %+v", i, r)
|
||||
}
|
||||
if r.Mode != "real" {
|
||||
t.Errorf("result %d mode = %q, want %q", i, r.Mode, "real")
|
||||
}
|
||||
if r.HTTPStatus != http.StatusNoContent {
|
||||
t.Errorf("result %d status = %d, want 204", i, r.HTTPStatus)
|
||||
}
|
||||
if r.Delay < 1 || r.ConnectMs < 1 || r.TTFBMs < 1 {
|
||||
t.Errorf("result %d timing not populated: %+v", i, r)
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
totalRequests := 0
|
||||
for addr, n := range requestsPerConn {
|
||||
totalRequests += n
|
||||
if n != 1 {
|
||||
t.Errorf("connection %s served %d requests, want 1 (real mode must skip the warm request)", addr, n)
|
||||
}
|
||||
}
|
||||
if totalRequests != 2 {
|
||||
t.Errorf("test URL served %d requests, want 2 (one cold request per probe)", totalRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestOutboundsTCPModeForcesUDPToHTTPProbe(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
withStubProcess(t, func(cfg *xray.Config, configPath string) batchProcess {
|
||||
return &stubProcess{cfg: cfg, serveSocks: true}
|
||||
})
|
||||
|
||||
batch := mustJSON(t, []any{map[string]any{"tag": "wg", "protocol": "wireguard"}})
|
||||
results, err := (&OutboundService{}).TestOutbounds(batch, srv.URL, "", "tcp")
|
||||
if err != nil {
|
||||
t.Fatalf("TestOutbounds: %v", err)
|
||||
}
|
||||
r := results[0]
|
||||
if !r.Success || r.Mode != "http" {
|
||||
t.Errorf("UDP outbound in tcp mode = %+v, want success with mode %q", r, "http")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeModeLabel(t *testing.T) {
|
||||
cases := []struct{ mode, want string }{
|
||||
{"tcp", "tcp"},
|
||||
{"real", "real"},
|
||||
{"http", "http"},
|
||||
{"", "http"},
|
||||
{"bogus", "http"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := probeModeLabel(c.mode); got != c.want {
|
||||
t.Errorf("probeModeLabel(%q) = %q, want %q", c.mode, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeThroughSocksTransportFailure(t *testing.T) {
|
||||
// A listener that accepts and immediately closes — SOCKS handshake dies.
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
@@ -482,7 +570,7 @@ func TestProbeThroughSocksTransportFailure(t *testing.T) {
|
||||
}()
|
||||
|
||||
var result TestOutboundResult
|
||||
probeThroughSocks(l.Addr().(*net.TCPAddr).Port, "http://127.0.0.1:9/", 2*time.Second, &result)
|
||||
probeThroughSocks(l.Addr().(*net.TCPAddr).Port, "http://127.0.0.1:9/", 2*time.Second, false, &result)
|
||||
if result.Success || result.Error == "" {
|
||||
t.Errorf("expected transport failure, got %+v", result)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user