mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-10 23:25:02 +03:00
fix(tests): stabilize local network fixtures
Keep the TUIC stream fragmentation check on a working UDP loopback path, allocate the AmneziaWG fixture ports dynamically, and wait for a real Xray stats response before starting API mutation assertions. Recreate failed readiness channels so an early connection cannot stall the probe. Verified host and offline parallel Go suites, repeated occupied-port and UDP tests, race/shuffle runs, and golangci-lint. Production behavior and dependency versions are unchanged. Split out of #6877 so that pull request's squash carries only the feature.
This commit is contained in:
@@ -286,12 +286,12 @@ func TestForwardedPortsOnlyChangeStillReconcilesPortForwards(t *testing.T) {
|
|||||||
t.Fatalf("generate peer keypair: %v", err)
|
t.Fatalf("generate peer keypair: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const forwardedPort = 58930
|
forwardedPort := freePort(t)
|
||||||
m := &Manager{ifaces: map[int]*managed{}}
|
m := &Manager{ifaces: map[int]*managed{}}
|
||||||
inst := amneziawg.Instance{
|
inst := amneziawg.Instance{
|
||||||
Id: 6,
|
Id: 6,
|
||||||
InterfaceName: "awgtest6",
|
InterfaceName: "awgtest6",
|
||||||
ListenPort: 58716,
|
ListenPort: 0,
|
||||||
PrivateKey: priv,
|
PrivateKey: priv,
|
||||||
PublicKey: pub,
|
PublicKey: pub,
|
||||||
Address: []string{"10.205.0.1/24"},
|
Address: []string{"10.205.0.1/24"},
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ func TestServerUDPStreamE2E(t *testing.T) {
|
|||||||
|
|
||||||
func testServerUDPStreamE2E(t *testing.T, controller string) {
|
func testServerUDPStreamE2E(t *testing.T, controller string) {
|
||||||
certPEM, keyPEM := generateTestCert(t)
|
certPEM, keyPEM := generateTestCert(t)
|
||||||
socksAddr, socksCleanup := startMockSocks5Server(t, "stream@example.com", "mock-socks-pass")
|
socksAddr, socksCleanup := startMockSocks5ServerWithUDPLoopback(t, udpEchoLoopback(t), "stream@example.com", "mock-socks-pass")
|
||||||
defer socksCleanup()
|
defer socksCleanup()
|
||||||
|
|
||||||
testUUID := uuid.New()
|
testUUID := uuid.New()
|
||||||
|
|||||||
@@ -179,7 +179,24 @@ func TestPipeBiDirectional(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func udpEchoLoopback(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
conn, err := net.ListenPacket("udp6", "[::1]:0")
|
||||||
|
if err == nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return "::1"
|
||||||
|
}
|
||||||
|
t.Logf("IPv6 loopback unavailable for SOCKS UDP echo: %v; using IPv4 loopback", err)
|
||||||
|
return "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
func startMockSocks5Server(t *testing.T, expectedUser, expectedPass string) (string, func()) {
|
func startMockSocks5Server(t *testing.T, expectedUser, expectedPass string) (string, func()) {
|
||||||
|
t.Helper()
|
||||||
|
return startMockSocks5ServerWithUDPLoopback(t, "127.0.0.1", expectedUser, expectedPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startMockSocks5ServerWithUDPLoopback(t *testing.T, udpIP, expectedUser, expectedPass string) (string, func()) {
|
||||||
|
t.Helper()
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to listen: %v", err)
|
t.Fatalf("failed to listen: %v", err)
|
||||||
@@ -198,7 +215,7 @@ func startMockSocks5Server(t *testing.T, expectedUser, expectedPass string) (str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
go handleMockSocksConn(conn, expectedUser, expectedPass)
|
go handleMockSocksConnWithUDPLoopback(conn, udpIP, expectedUser, expectedPass)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -209,6 +226,10 @@ func startMockSocks5Server(t *testing.T, expectedUser, expectedPass string) (str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleMockSocksConn(conn net.Conn, expectedUser, expectedPass string) {
|
func handleMockSocksConn(conn net.Conn, expectedUser, expectedPass string) {
|
||||||
|
handleMockSocksConnWithUDPLoopback(conn, "127.0.0.1", expectedUser, expectedPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMockSocksConnWithUDPLoopback(conn net.Conn, udpIP, expectedUser, expectedPass string) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
// Read greeting
|
// Read greeting
|
||||||
var greeting [4]byte
|
var greeting [4]byte
|
||||||
@@ -277,7 +298,7 @@ func handleMockSocksConn(conn net.Conn, expectedUser, expectedPass string) {
|
|||||||
_, _ = io.Copy(conn, conn)
|
_, _ = io.Copy(conn, conn)
|
||||||
case 0x03: // UDP ASSOCIATE
|
case 0x03: // UDP ASSOCIATE
|
||||||
// Bind a UDP listener for the mock
|
// Bind a UDP listener for the mock
|
||||||
u, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
|
u, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(udpIP), Port: 0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -285,13 +306,19 @@ func handleMockSocksConn(conn net.Conn, expectedUser, expectedPass string) {
|
|||||||
bindAddr := u.LocalAddr().(*net.UDPAddr)
|
bindAddr := u.LocalAddr().(*net.UDPAddr)
|
||||||
bindPort := uint16(bindAddr.Port)
|
bindPort := uint16(bindAddr.Port)
|
||||||
|
|
||||||
resp := make([]byte, 10)
|
var resp []byte
|
||||||
|
if bindAddr.IP.To4() != nil {
|
||||||
|
resp = make([]byte, 10)
|
||||||
|
resp[3] = 0x01
|
||||||
|
copy(resp[4:8], bindAddr.IP.To4())
|
||||||
|
binary.BigEndian.PutUint16(resp[8:10], bindPort)
|
||||||
|
} else {
|
||||||
|
resp = make([]byte, 22)
|
||||||
|
resp[3] = 0x04
|
||||||
|
copy(resp[4:20], bindAddr.IP.To16())
|
||||||
|
binary.BigEndian.PutUint16(resp[20:22], bindPort)
|
||||||
|
}
|
||||||
resp[0] = 0x05
|
resp[0] = 0x05
|
||||||
resp[1] = 0x00
|
|
||||||
resp[2] = 0x00
|
|
||||||
resp[3] = 0x01
|
|
||||||
copy(resp[4:8], bindAddr.IP.To4())
|
|
||||||
binary.BigEndian.PutUint16(resp[8:10], bindPort)
|
|
||||||
if _, err := conn.Write(resp); err != nil {
|
if _, err := conn.Write(resp); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package xray
|
package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -9,6 +10,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
statsService "github.com/xtls/xray-core/app/stats/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestXrayAPI_E2E exercises the gRPC hot-apply surface (outbounds, inbounds,
|
// TestXrayAPI_E2E exercises the gRPC hot-apply surface (outbounds, inbounds,
|
||||||
@@ -87,6 +90,7 @@ func TestXrayAPI_E2E(t *testing.T) {
|
|||||||
t.Fatalf("api init: %v", err)
|
t.Fatalf("api init: %v", err)
|
||||||
}
|
}
|
||||||
defer api.Close()
|
defer api.Close()
|
||||||
|
waitForXrayAPIReady(t, &api, apiPort)
|
||||||
|
|
||||||
// --- outbounds ---
|
// --- outbounds ---
|
||||||
socksOutbound := []byte(`{"protocol":"socks","settings":{"servers":[{"address":"127.0.0.1","port":10808}]},"tag":"test-out"}`)
|
socksOutbound := []byte(`{"protocol":"socks","settings":{"servers":[{"address":"127.0.0.1","port":10808}]},"tag":"test-out"}`)
|
||||||
@@ -255,3 +259,40 @@ func waitForPort(t *testing.T, port int) {
|
|||||||
}
|
}
|
||||||
t.Fatalf("xray api port %d did not open in time", port)
|
t.Fatalf("xray api port %d did not open in time", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func waitForXrayAPIReady(t *testing.T, api *XrayAPI, apiPort int) {
|
||||||
|
t.Helper()
|
||||||
|
deadline := time.NewTimer(15 * time.Second)
|
||||||
|
defer deadline.Stop()
|
||||||
|
ticker := time.NewTicker(100 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for {
|
||||||
|
if api.StatsServiceClient == nil {
|
||||||
|
if err := api.Init(apiPort); err != nil {
|
||||||
|
t.Fatalf("reinitialize Xray API client: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
resp, err := (*api.StatsServiceClient).GetSysStats(ctx, &statsService.SysStatsRequest{})
|
||||||
|
cancel()
|
||||||
|
if err == nil && resp != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
} else {
|
||||||
|
lastErr = fmt.Errorf("GetSysStats returned a nil response")
|
||||||
|
}
|
||||||
|
|
||||||
|
api.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-deadline.C:
|
||||||
|
t.Fatalf("xray API did not become ready: %v", lastErr)
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ func startE2ECore(t *testing.T, inbounds []any) *e2eCore {
|
|||||||
t.Fatalf("api init: %v", err)
|
t.Fatalf("api init: %v", err)
|
||||||
}
|
}
|
||||||
t.Cleanup(api.Close)
|
t.Cleanup(api.Close)
|
||||||
|
waitForXrayAPIReady(t, api, apiPort)
|
||||||
return &e2eCore{api: api, cmd: cmd, port: apiPort}
|
return &e2eCore{api: api, cmd: cmd, port: apiPort}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user