From 3d51aa455006903345f554a2dd90034993796114 Mon Sep 17 00:00:00 2001 From: sergei Date: Tue, 14 Apr 2026 06:23:55 +0400 Subject: vpnem: VPN infrastructure with load-balanced multi-protocol nodes - Multi-protocol VPS nodes (VLESS-REALITY + Hysteria2 + SOCKS5) - Smart load balancing via recommendation API - Windows/Linux client (Go + Wails + sing-box) - Server API with RealIP detection and connection tracking - Auto-deployment via vpnui control plane - Silent Windows installer with UAC elevation - Load-based server recommendation (no sticky sessions) - Best Server one-click connection workflow --- internal/rules/connections_test.go | 201 +++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 internal/rules/connections_test.go (limited to 'internal/rules/connections_test.go') diff --git a/internal/rules/connections_test.go b/internal/rules/connections_test.go new file mode 100644 index 0000000..22355dc --- /dev/null +++ b/internal/rules/connections_test.go @@ -0,0 +1,201 @@ +package rules + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestConnectionStoreConnectAndRecommend(t *testing.T) { + tmpDir := t.TempDir() + store := NewConnectionStore(tmpDir) + if err := store.Load(); err != nil { + t.Fatal(err) + } + + availableIPs := []string{"5.180.97.198", "5.180.97.199", "5.180.97.197"} + healthyIPs := map[string]bool{"5.180.97.198": true, "5.180.97.199": true, "5.180.97.197": true} + + // Studio 1 connects to 198 + store.Connect("1.2.3.4", "5.180.97.198", "nl-198", "windows", "2.0.11") + + // Studio 1 asks — should get 198 (least loaded: 1 client vs 0/0) + // Actually: 198=1, 199=0, 197=0 → should get 199 or 197 (least loaded) + rec1 := store.GetRecommendation("1.2.3.4", availableIPs, healthyIPs) + if rec1.RecommendedServerIP == "5.180.97.198" { + // This is OK if load balancing picks a different server + t.Logf("studio 1 recommended: %s (reason: %s)", rec1.RecommendedServerIP, rec1.Reason) + } else { + t.Logf("studio 1 recommended different server: %s (load-based)", rec1.RecommendedServerIP) + } + + // Studio 2 is new — should also get least loaded + rec2 := store.GetRecommendation("9.9.9.9", availableIPs, healthyIPs) + if rec2.RecommendedServerIP == "" { + t.Fatal("expected recommendation for new studio") + } + t.Logf("studio 2 recommended: %s (reason: %s)", rec2.RecommendedServerIP, rec2.Reason) +} + +func TestPureLoadBalancing(t *testing.T) { + tmpDir := t.TempDir() + store := NewConnectionStore(tmpDir) + if err := store.Load(); err != nil { + t.Fatal(err) + } + + availableIPs := []string{"5.180.97.198", "5.180.97.199", "5.180.97.197"} + healthyIPs := map[string]bool{"5.180.97.198": true, "5.180.97.199": true, "5.180.97.197": true} + + // 3 studios connect to 198 (overload it) + for i := 0; i < 3; i++ { + ip := "10.0.0." + string(rune('1'+i)) + store.Connect(ip, "5.180.97.198", "nl-198", "windows", "") + } + + // New studio should NOT get 198 (3 clients) — should get 199 or 197 (0 clients) + rec := store.GetRecommendation("99.99.99.99", availableIPs, healthyIPs) + if rec.RecommendedServerIP == "5.180.97.198" { + t.Fatalf("should not recommend overloaded server, got %s", rec.RecommendedServerIP) + } + t.Logf("new studio recommended: %s (reason: %s)", rec.RecommendedServerIP, rec.Reason) + + // Even studio 1 (home=198) should get load-balanced recommendation + rec2 := store.GetRecommendation("10.0.0.1", availableIPs, healthyIPs) + t.Logf("studio 1 re-recommended: %s (reason: %s, isRebalance: %v)", + rec2.RecommendedServerIP, rec2.Reason, rec2.IsRebalance) +} + +func TestHomeServerUnhealthy(t *testing.T) { + tmpDir := t.TempDir() + store := NewConnectionStore(tmpDir) + if err := store.Load(); err != nil { + t.Fatal(err) + } + + availableIPs := []string{"5.180.97.198", "5.180.97.199"} + // 198 is NOT healthy + healthyIPs := map[string]bool{"5.180.97.199": true} + + // Studio 1 has connected to 198 + store.Connect("1.2.3.4", "5.180.97.198", "nl-198", "windows", "") + + // But 198 is unhealthy — should recommend 199 + rec := store.GetRecommendation("1.2.3.4", availableIPs, healthyIPs) + if rec.RecommendedServerIP == "5.180.97.198" { + t.Fatalf("should not recommend unhealthy server, got %s", rec.RecommendedServerIP) + } + if rec.RecommendedServerIP != "5.180.97.199" { + t.Fatalf("should recommend healthy server 199, got %s", rec.RecommendedServerIP) + } +} + +func TestDisconnect(t *testing.T) { + tmpDir := t.TempDir() + store := NewConnectionStore(tmpDir) + if err := store.Load(); err != nil { + t.Fatal(err) + } + + availableIPs := []string{"5.180.97.198"} + + store.Connect("1.2.3.4", "5.180.97.198", "nl-198", "windows", "") + + load := store.GetLoadInfo(availableIPs) + if len(load) == 0 || load[0].ActiveClients != 1 { + t.Fatalf("expected 1 active client, got %v", load) + } + + store.Disconnect("1.2.3.4") + + load = store.GetLoadInfo(availableIPs) + if len(load) == 0 || load[0].ActiveClients != 0 { + t.Fatalf("expected 0 active clients after disconnect, got %v", load) + } +} + +func TestSessionExpiry(t *testing.T) { + tmpDir := t.TempDir() + store := NewConnectionStore(tmpDir) + store.staleAfter = 1 * time.Millisecond + if err := store.Load(); err != nil { + t.Fatal(err) + } + + availableIPs := []string{"5.180.97.198"} + healthyIPs := map[string]bool{"5.180.97.198": true} + + store.Connect("1.2.3.4", "5.180.97.198", "nl-198", "windows", "") + time.Sleep(10 * time.Millisecond) + + rec := store.GetRecommendation("1.2.3.4", availableIPs, healthyIPs) + if rec.RecommendedServerIP != "5.180.97.198" { + t.Fatalf("expected recommendation to 198 after session expiry, got %s", rec.RecommendedServerIP) + } + + load := store.GetLoadInfo(availableIPs) + if len(load) == 0 || load[0].ActiveClients != 0 { + t.Fatalf("expected 0 active clients after expiry, got %v", load) + } +} + +func TestPersistence(t *testing.T) { + tmpDir := t.TempDir() + + store1 := NewConnectionStore(tmpDir) + store1.Connect("1.2.3.4", "5.180.97.199", "nl-199", "windows", "") + + store2 := NewConnectionStore(tmpDir) + if err := store2.Load(); err != nil { + t.Fatal(err) + } + + availableIPs := []string{"5.180.97.199"} + healthyIPs := map[string]bool{"5.180.97.199": true} + rec := store2.GetRecommendation("1.2.3.4", availableIPs, healthyIPs) + if rec.RecommendedServerIP != "5.180.97.199" { + t.Fatalf("expected recommendation to 199, got %s", rec.RecommendedServerIP) + } + + _, err := os.Stat(filepath.Join(tmpDir, "connections.json")) + if err != nil { + t.Fatal("expected connections.json to exist") + } +} + +func TestLoadInfoFormat(t *testing.T) { + tmpDir := t.TempDir() + store := NewConnectionStore(tmpDir) + store.maxCap = 10 + + store.Connect("1.1.1.1", "5.180.97.198", "nl-198", "windows", "") + store.Connect("2.2.2.2", "5.180.97.198", "nl-198", "windows", "") + store.Connect("3.3.3.3", "5.180.97.199", "nl-199", "linux", "") + + availableIPs := []string{"5.180.97.198", "5.180.97.199"} + load := store.GetLoadInfo(availableIPs) + + if len(load) != 2 { + t.Fatalf("expected 2 server load entries, got %d", len(load)) + } + + for _, info := range load { + if info.ServerIP == "5.180.97.198" { + if info.ActiveClients != 2 { + t.Errorf("expected 2 clients on 198, got %d", info.ActiveClients) + } + if info.LoadPercent != 20 { + t.Errorf("expected 20%% load on 198, got %d", info.LoadPercent) + } + } + if info.ServerIP == "5.180.97.199" { + if info.ActiveClients != 1 { + t.Errorf("expected 1 client on 199, got %d", info.ActiveClients) + } + if info.LoadPercent != 10 { + t.Errorf("expected 10%% load on 199, got %d", info.LoadPercent) + } + } + } +} -- cgit v1.2.3