xray_wireguard_config_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package service
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. )
  9. func wgTestSecretKey() string {
  10. return base64.StdEncoding.EncodeToString(make([]byte, 32))
  11. }
  12. func wgInboundEmittedSettings(t *testing.T, tag string) map[string]any {
  13. t.Helper()
  14. svc := &XrayService{}
  15. cfg, err := svc.GetXrayConfig()
  16. if err != nil {
  17. t.Fatalf("GetXrayConfig: %v", err)
  18. }
  19. for i := range cfg.InboundConfigs {
  20. ic := cfg.InboundConfigs[i]
  21. if ic.Tag != tag {
  22. continue
  23. }
  24. var s map[string]any
  25. if err := json.Unmarshal([]byte(ic.Settings), &s); err != nil {
  26. t.Fatalf("unmarshal emitted settings: %v", err)
  27. }
  28. return s
  29. }
  30. t.Fatalf("inbound %q not found in generated config", tag)
  31. return nil
  32. }
  33. func seedWGInbound(t *testing.T, tag string, port int, clients []model.Client) {
  34. t.Helper()
  35. setupSettingTestDB(t)
  36. db := database.GetDB()
  37. in := &model.Inbound{
  38. Tag: tag,
  39. Enable: true,
  40. Port: port,
  41. Protocol: model.WireGuard,
  42. Settings: `{"secretKey":"` + wgTestSecretKey() + `","mtu":1420}`,
  43. }
  44. if err := db.Create(in).Error; err != nil {
  45. t.Fatalf("create wg inbound: %v", err)
  46. }
  47. svc := ClientService{}
  48. if err := svc.SyncInbound(nil, in.Id, clients); err != nil {
  49. t.Fatalf("SyncInbound: %v", err)
  50. }
  51. }
  52. func seedDualTunnelClient(t *testing.T, enabled bool) string {
  53. t.Helper()
  54. setupSettingTestDB(t)
  55. db := database.GetDB()
  56. const email = "[email protected]"
  57. wgClient := model.Client{
  58. Email: email,
  59. Enable: true,
  60. PublicKey: "pub-dual",
  61. AllowedIPs: []string{"10.0.0.5/32"},
  62. PreSharedKey: "wg-psk",
  63. }
  64. awgClient := wgClient
  65. awgClient.AllowedIPs = []string{"10.8.1.5/32"}
  66. awgClient.PreSharedKey = "awg-psk"
  67. wgSettings, err := json.Marshal(map[string]any{
  68. "secretKey": wgTestSecretKey(),
  69. "mtu": 1420,
  70. "clients": []model.Client{wgClient},
  71. })
  72. if err != nil {
  73. t.Fatalf("marshal wg settings: %v", err)
  74. }
  75. awgSettings, err := json.Marshal(map[string]any{
  76. "server": map[string]any{"subnetIp": "10.8.1.0", "subnetCidr": 24},
  77. "clients": []model.Client{awgClient},
  78. })
  79. if err != nil {
  80. t.Fatalf("marshal awg settings: %v", err)
  81. }
  82. wgInbound := &model.Inbound{Tag: "wg-dual", Enable: true, Port: 51823, Protocol: model.WireGuard, Settings: string(wgSettings)}
  83. awgInbound := &model.Inbound{Tag: "awg-dual", Enable: true, Port: 51824, Protocol: model.AmneziaWG, Settings: string(awgSettings)}
  84. if err := db.Create(wgInbound).Error; err != nil {
  85. t.Fatalf("create wg inbound: %v", err)
  86. }
  87. if err := db.Create(awgInbound).Error; err != nil {
  88. t.Fatalf("create awg inbound: %v", err)
  89. }
  90. svc := ClientService{}
  91. if err := svc.SyncInbound(nil, wgInbound.Id, []model.Client{wgClient}); err != nil {
  92. t.Fatalf("SyncInbound(wg): %v", err)
  93. }
  94. awgClient.Enable = enabled
  95. if err := svc.SyncInbound(nil, awgInbound.Id, []model.Client{awgClient}); err != nil {
  96. t.Fatalf("SyncInbound(awg): %v", err)
  97. }
  98. return email
  99. }
  100. func wgPeerList(t *testing.T, settings map[string]any) []map[string]any {
  101. t.Helper()
  102. if _, ok := settings["clients"]; ok {
  103. t.Fatalf("wireguard inbound must not emit a clients[] key: %v", settings["clients"])
  104. }
  105. rawPeers, ok := settings["peers"].([]any)
  106. if !ok {
  107. t.Fatalf("settings.peers is not an array: %T", settings["peers"])
  108. }
  109. out := make([]map[string]any, 0, len(rawPeers))
  110. for _, p := range rawPeers {
  111. m, ok := p.(map[string]any)
  112. if !ok {
  113. t.Fatalf("peer is not an object: %T", p)
  114. }
  115. out = append(out, m)
  116. }
  117. return out
  118. }
  119. func TestGetXrayConfigWireGuardPeers(t *testing.T) {
  120. clients := []model.Client{
  121. {Email: "[email protected]", Enable: true, PublicKey: "pub-alice", AllowedIPs: []string{"10.0.0.2/32"}, KeepAlive: 25},
  122. {Email: "[email protected]", Enable: true, PublicKey: "pub-bob", AllowedIPs: []string{"10.0.0.3/32"}},
  123. }
  124. seedWGInbound(t, "wg-multi", 51820, clients)
  125. settings := wgInboundEmittedSettings(t, "wg-multi")
  126. if settings["secretKey"] != wgTestSecretKey() {
  127. t.Errorf("secretKey not preserved: %v", settings["secretKey"])
  128. }
  129. if settings["mtu"] != float64(1420) {
  130. t.Errorf("mtu not preserved: %v", settings["mtu"])
  131. }
  132. peers := wgPeerList(t, settings)
  133. if len(peers) != 2 {
  134. t.Fatalf("expected 2 peers, got %d: %v", len(peers), peers)
  135. }
  136. ips := map[string]bool{}
  137. for _, p := range peers {
  138. if p["email"] == nil || p["email"] == "" {
  139. t.Errorf("peer missing email: %v", p)
  140. }
  141. if p["publicKey"] == nil || p["publicKey"] == "" {
  142. t.Errorf("peer missing publicKey: %v", p)
  143. }
  144. if p["level"] != float64(0) {
  145. t.Errorf("peer level = %v, want 0 (needed for per-user stats)", p["level"])
  146. }
  147. allowed, ok := p["allowedIPs"].([]any)
  148. if !ok || len(allowed) == 0 {
  149. t.Fatalf("peer missing allowedIPs: %v", p)
  150. }
  151. ips[allowed[0].(string)] = true
  152. }
  153. if len(ips) != 2 {
  154. t.Errorf("peers must have distinct allowedIPs, got %v", ips)
  155. }
  156. }
  157. func TestGetXrayConfigWireGuardDisabledClientExcluded(t *testing.T) {
  158. clients := []model.Client{
  159. {Email: "[email protected]", Enable: true, PublicKey: "pub-on", AllowedIPs: []string{"10.0.0.2/32"}},
  160. {Email: "[email protected]", Enable: true, PublicKey: "pub-off", AllowedIPs: []string{"10.0.0.3/32"}},
  161. }
  162. seedWGInbound(t, "wg-disabled", 51821, clients)
  163. if err := database.GetDB().Model(&model.ClientRecord{}).
  164. Where("email = ?", "[email protected]").Update("enable", false).Error; err != nil {
  165. t.Fatalf("disable client: %v", err)
  166. }
  167. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-disabled"))
  168. if len(peers) != 1 {
  169. t.Fatalf("expected 1 enabled peer, got %d: %v", len(peers), peers)
  170. }
  171. if peers[0]["email"] != "[email protected]" {
  172. t.Errorf("wrong peer kept: %v", peers[0])
  173. }
  174. }
  175. func TestGetXrayConfigWireGuardUsesInboundLocalTunnelFields(t *testing.T) {
  176. email := seedDualTunnelClient(t, true)
  177. var shared model.ClientRecord
  178. if err := database.GetDB().Where("email = ?", email).First(&shared).Error; err != nil {
  179. t.Fatalf("read shared client: %v", err)
  180. }
  181. if shared.AllowedIPs != "10.8.1.5/32" || shared.PreSharedKey != "awg-psk" {
  182. t.Fatalf("test setup did not persist AmneziaWG last: allowedIPs=%q preSharedKey=%q", shared.AllowedIPs, shared.PreSharedKey)
  183. }
  184. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
  185. if len(peers) != 1 {
  186. t.Fatalf("expected 1 peer, got %d: %v", len(peers), peers)
  187. }
  188. allowed, ok := peers[0]["allowedIPs"].([]any)
  189. if !ok || len(allowed) != 1 || allowed[0] != "10.0.0.5/32" {
  190. t.Fatalf("WireGuard peer allowedIPs = %v, want [10.0.0.5/32]", peers[0]["allowedIPs"])
  191. }
  192. if peers[0]["preSharedKey"] != "wg-psk" {
  193. t.Fatalf("WireGuard peer preSharedKey = %v, want wg-psk", peers[0]["preSharedKey"])
  194. }
  195. }
  196. func TestGetXrayConfigWireGuardDisabledDualProtocolClientExcluded(t *testing.T) {
  197. seedDualTunnelClient(t, false)
  198. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
  199. if len(peers) != 0 {
  200. t.Fatalf("expected disabled dual-protocol client to be excluded, got %v", peers)
  201. }
  202. }
  203. func TestGetXrayConfigWireGuardNoClientsEmitsEmptyPeers(t *testing.T) {
  204. seedWGInbound(t, "wg-empty", 51822, nil)
  205. settings := wgInboundEmittedSettings(t, "wg-empty")
  206. if _, ok := settings["clients"]; ok {
  207. t.Fatalf("clients key must be absent")
  208. }
  209. peers, ok := settings["peers"].([]any)
  210. if !ok {
  211. t.Fatalf("peers must be an (empty) array, got %T", settings["peers"])
  212. }
  213. if len(peers) != 0 {
  214. t.Fatalf("expected empty peers, got %v", peers)
  215. }
  216. }