瀏覽代碼

fix: preserve per-inbound WireGuard peer addresses (#6344)

Clients are stored once per email in the client table, so when the same email
exists on more than one WireGuard inbound the shared record's AllowedIPs and
PreSharedKey win for every inbound. A client present on both a WG and an AWG
tunnel was emitted with one tunnel's address on both, so the second tunnel's
peer got the wrong allowedIPs.

Read the per-inbound client settings for WireGuard inbounds and, when the
inbound carries its own entry for that email, use its AllowedIPs and
PreSharedKey when building the peer.
Matt Van Horn 17 小時之前
父節點
當前提交
f64453041a
共有 2 個文件被更改,包括 100 次插入0 次删除
  1. 14 0
      internal/web/service/xray.go
  2. 86 0
      internal/web/service/xray_wireguard_config_test.go

+ 14 - 0
internal/web/service/xray.go

@@ -179,6 +179,16 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 		}
 		settings := map[string]any{}
 		_ = json.Unmarshal([]byte(inbound.Settings), &settings)
+		var wireguardClientsByEmail map[string]model.Client
+		if inbound.Protocol == model.WireGuard {
+			inboundClients, _ := ParseInboundSettingsClients(inbound.Settings)
+			if len(inboundClients) > 0 {
+				wireguardClientsByEmail = make(map[string]model.Client, len(inboundClients))
+				for _, client := range inboundClients {
+					wireguardClientsByEmail[strings.ToLower(strings.TrimSpace(client.Email))] = client
+				}
+			}
+		}
 
 		dbClients, listErr := s.inboundService.clientService.ListForInbound(nil, inbound.Id)
 		if listErr != nil {
@@ -244,6 +254,10 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 					entry["auth"] = c.Auth
 				}
 			case model.WireGuard:
+				if inboundClient, ok := wireguardClientsByEmail[strings.ToLower(strings.TrimSpace(c.Email))]; ok {
+					c.AllowedIPs = inboundClient.AllowedIPs
+					c.PreSharedKey = inboundClient.PreSharedKey
+				}
 				wgPeers = append(wgPeers, model.WireguardPeerFromClient(c))
 				continue
 			}

+ 86 - 0
internal/web/service/xray_wireguard_config_test.go

@@ -55,6 +55,59 @@ func seedWGInbound(t *testing.T, tag string, port int, clients []model.Client) {
 	}
 }
 
+func seedDualTunnelClient(t *testing.T, enabled bool) string {
+	t.Helper()
+	setupSettingTestDB(t)
+	db := database.GetDB()
+
+	const email = "[email protected]"
+	wgClient := model.Client{
+		Email:        email,
+		Enable:       true,
+		PublicKey:    "pub-dual",
+		AllowedIPs:   []string{"10.0.0.5/32"},
+		PreSharedKey: "wg-psk",
+	}
+	awgClient := wgClient
+	awgClient.AllowedIPs = []string{"10.8.1.5/32"}
+	awgClient.PreSharedKey = "awg-psk"
+
+	wgSettings, err := json.Marshal(map[string]any{
+		"secretKey": wgTestSecretKey(),
+		"mtu":       1420,
+		"clients":   []model.Client{wgClient},
+	})
+	if err != nil {
+		t.Fatalf("marshal wg settings: %v", err)
+	}
+	awgSettings, err := json.Marshal(map[string]any{
+		"server":  map[string]any{"subnetIp": "10.8.1.0", "subnetCidr": 24},
+		"clients": []model.Client{awgClient},
+	})
+	if err != nil {
+		t.Fatalf("marshal awg settings: %v", err)
+	}
+
+	wgInbound := &model.Inbound{Tag: "wg-dual", Enable: true, Port: 51823, Protocol: model.WireGuard, Settings: string(wgSettings)}
+	awgInbound := &model.Inbound{Tag: "awg-dual", Enable: true, Port: 51824, Protocol: model.AmneziaWG, Settings: string(awgSettings)}
+	if err := db.Create(wgInbound).Error; err != nil {
+		t.Fatalf("create wg inbound: %v", err)
+	}
+	if err := db.Create(awgInbound).Error; err != nil {
+		t.Fatalf("create awg inbound: %v", err)
+	}
+
+	svc := ClientService{}
+	if err := svc.SyncInbound(nil, wgInbound.Id, []model.Client{wgClient}); err != nil {
+		t.Fatalf("SyncInbound(wg): %v", err)
+	}
+	awgClient.Enable = enabled
+	if err := svc.SyncInbound(nil, awgInbound.Id, []model.Client{awgClient}); err != nil {
+		t.Fatalf("SyncInbound(awg): %v", err)
+	}
+	return email
+}
+
 func wgPeerList(t *testing.T, settings map[string]any) []map[string]any {
 	t.Helper()
 	if _, ok := settings["clients"]; ok {
@@ -137,6 +190,39 @@ func TestGetXrayConfigWireGuardDisabledClientExcluded(t *testing.T) {
 	}
 }
 
+func TestGetXrayConfigWireGuardUsesInboundLocalTunnelFields(t *testing.T) {
+	email := seedDualTunnelClient(t, true)
+
+	var shared model.ClientRecord
+	if err := database.GetDB().Where("email = ?", email).First(&shared).Error; err != nil {
+		t.Fatalf("read shared client: %v", err)
+	}
+	if shared.AllowedIPs != "10.8.1.5/32" || shared.PreSharedKey != "awg-psk" {
+		t.Fatalf("test setup did not persist AmneziaWG last: allowedIPs=%q preSharedKey=%q", shared.AllowedIPs, shared.PreSharedKey)
+	}
+
+	peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
+	if len(peers) != 1 {
+		t.Fatalf("expected 1 peer, got %d: %v", len(peers), peers)
+	}
+	allowed, ok := peers[0]["allowedIPs"].([]any)
+	if !ok || len(allowed) != 1 || allowed[0] != "10.0.0.5/32" {
+		t.Fatalf("WireGuard peer allowedIPs = %v, want [10.0.0.5/32]", peers[0]["allowedIPs"])
+	}
+	if peers[0]["preSharedKey"] != "wg-psk" {
+		t.Fatalf("WireGuard peer preSharedKey = %v, want wg-psk", peers[0]["preSharedKey"])
+	}
+}
+
+func TestGetXrayConfigWireGuardDisabledDualProtocolClientExcluded(t *testing.T) {
+	seedDualTunnelClient(t, false)
+
+	peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
+	if len(peers) != 0 {
+		t.Fatalf("expected disabled dual-protocol client to be excluded, got %v", peers)
+	}
+}
+
 func TestGetXrayConfigWireGuardNoClientsEmitsEmptyPeers(t *testing.T) {
 	seedWGInbound(t, "wg-empty", 51822, nil)