xray_wireguard_config_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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, wgKeepAlive *int) 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-wg",
  61. AllowedIPs: []string{"10.0.0.5/32"},
  62. PreSharedKey: "wg-psk",
  63. KeepAlive: wgKeepAlive,
  64. }
  65. awgClient := wgClient
  66. awgClient.PublicKey = "pub-awg"
  67. awgClient.AllowedIPs = []string{"10.8.1.5/32"}
  68. awgClient.PreSharedKey = "awg-psk"
  69. awgClient.KeepAlive = model.KeepAlivePtr(25)
  70. wgSettings, err := json.Marshal(map[string]any{
  71. "secretKey": wgTestSecretKey(),
  72. "mtu": 1420,
  73. "clients": []model.Client{wgClient},
  74. })
  75. if err != nil {
  76. t.Fatalf("marshal wg settings: %v", err)
  77. }
  78. awgSettings, err := json.Marshal(map[string]any{
  79. "server": map[string]any{"subnetIp": "10.8.1.0", "subnetCidr": 24},
  80. "clients": []model.Client{awgClient},
  81. })
  82. if err != nil {
  83. t.Fatalf("marshal awg settings: %v", err)
  84. }
  85. wgInbound := &model.Inbound{Tag: "wg-dual", Enable: true, Port: 51823, Protocol: model.WireGuard, Settings: string(wgSettings)}
  86. awgInbound := &model.Inbound{Tag: "awg-dual", Enable: true, Port: 51824, Protocol: model.AmneziaWG, Settings: string(awgSettings)}
  87. if err := db.Create(wgInbound).Error; err != nil {
  88. t.Fatalf("create wg inbound: %v", err)
  89. }
  90. if err := db.Create(awgInbound).Error; err != nil {
  91. t.Fatalf("create awg inbound: %v", err)
  92. }
  93. svc := ClientService{}
  94. if err := svc.SyncInbound(nil, wgInbound.Id, []model.Client{wgClient}); err != nil {
  95. t.Fatalf("SyncInbound(wg): %v", err)
  96. }
  97. awgClient.Enable = enabled
  98. if err := svc.SyncInbound(nil, awgInbound.Id, []model.Client{awgClient}); err != nil {
  99. t.Fatalf("SyncInbound(awg): %v", err)
  100. }
  101. return email
  102. }
  103. func wgPeerList(t *testing.T, settings map[string]any) []map[string]any {
  104. t.Helper()
  105. if _, ok := settings["clients"]; ok {
  106. t.Fatalf("wireguard inbound must not emit a clients[] key: %v", settings["clients"])
  107. }
  108. rawPeers, ok := settings["peers"].([]any)
  109. if !ok {
  110. t.Fatalf("settings.peers is not an array: %T", settings["peers"])
  111. }
  112. out := make([]map[string]any, 0, len(rawPeers))
  113. for _, p := range rawPeers {
  114. m, ok := p.(map[string]any)
  115. if !ok {
  116. t.Fatalf("peer is not an object: %T", p)
  117. }
  118. out = append(out, m)
  119. }
  120. return out
  121. }
  122. func TestGetXrayConfigWireGuardPeers(t *testing.T) {
  123. clients := []model.Client{
  124. {Email: "[email protected]", Enable: true, PublicKey: "pub-alice", AllowedIPs: []string{"10.0.0.2/32"}, KeepAlive: model.KeepAlivePtr(25)},
  125. {Email: "[email protected]", Enable: true, PublicKey: "pub-bob", AllowedIPs: []string{"10.0.0.3/32"}},
  126. }
  127. seedWGInbound(t, "wg-multi", 51820, clients)
  128. settings := wgInboundEmittedSettings(t, "wg-multi")
  129. if settings["secretKey"] != wgTestSecretKey() {
  130. t.Errorf("secretKey not preserved: %v", settings["secretKey"])
  131. }
  132. if settings["mtu"] != float64(1420) {
  133. t.Errorf("mtu not preserved: %v", settings["mtu"])
  134. }
  135. peers := wgPeerList(t, settings)
  136. if len(peers) != 2 {
  137. t.Fatalf("expected 2 peers, got %d: %v", len(peers), peers)
  138. }
  139. ips := map[string]bool{}
  140. for _, p := range peers {
  141. if p["email"] == nil || p["email"] == "" {
  142. t.Errorf("peer missing email: %v", p)
  143. }
  144. if p["publicKey"] == nil || p["publicKey"] == "" {
  145. t.Errorf("peer missing publicKey: %v", p)
  146. }
  147. if p["level"] != float64(0) {
  148. t.Errorf("peer level = %v, want 0 (needed for per-user stats)", p["level"])
  149. }
  150. allowed, ok := p["allowedIPs"].([]any)
  151. if !ok || len(allowed) == 0 {
  152. t.Fatalf("peer missing allowedIPs: %v", p)
  153. }
  154. ips[allowed[0].(string)] = true
  155. }
  156. if len(ips) != 2 {
  157. t.Errorf("peers must have distinct allowedIPs, got %v", ips)
  158. }
  159. }
  160. func TestGetXrayConfigWireGuardDisabledClientExcluded(t *testing.T) {
  161. clients := []model.Client{
  162. {Email: "[email protected]", Enable: true, PublicKey: "pub-on", AllowedIPs: []string{"10.0.0.2/32"}},
  163. {Email: "[email protected]", Enable: true, PublicKey: "pub-off", AllowedIPs: []string{"10.0.0.3/32"}},
  164. }
  165. seedWGInbound(t, "wg-disabled", 51821, clients)
  166. if err := database.GetDB().Model(&model.ClientRecord{}).
  167. Where("email = ?", "[email protected]").Update("enable", false).Error; err != nil {
  168. t.Fatalf("disable client: %v", err)
  169. }
  170. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-disabled"))
  171. if len(peers) != 1 {
  172. t.Fatalf("expected 1 enabled peer, got %d: %v", len(peers), peers)
  173. }
  174. if peers[0]["email"] != "[email protected]" {
  175. t.Errorf("wrong peer kept: %v", peers[0])
  176. }
  177. }
  178. func TestGetXrayConfigWireGuardUsesInboundLocalTunnelFields(t *testing.T) {
  179. email := seedDualTunnelClient(t, true, model.KeepAlivePtr(15))
  180. var shared model.ClientRecord
  181. if err := database.GetDB().Where("email = ?", email).First(&shared).Error; err != nil {
  182. t.Fatalf("read shared client: %v", err)
  183. }
  184. if shared.AllowedIPs != "10.8.1.5/32" || shared.PreSharedKey != "awg-psk" || shared.PublicKey != "pub-awg" || shared.KeepAlive != 25 {
  185. t.Fatalf("test setup did not persist AmneziaWG last: allowedIPs=%q preSharedKey=%q publicKey=%q keepAlive=%d", shared.AllowedIPs, shared.PreSharedKey, shared.PublicKey, shared.KeepAlive)
  186. }
  187. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
  188. if len(peers) != 1 {
  189. t.Fatalf("expected 1 peer, got %d: %v", len(peers), peers)
  190. }
  191. allowed, ok := peers[0]["allowedIPs"].([]any)
  192. if !ok || len(allowed) != 1 || allowed[0] != "10.0.0.5/32" {
  193. t.Fatalf("WireGuard peer allowedIPs = %v, want [10.0.0.5/32]", peers[0]["allowedIPs"])
  194. }
  195. if peers[0]["preSharedKey"] != "wg-psk" {
  196. t.Fatalf("WireGuard peer preSharedKey = %v, want wg-psk", peers[0]["preSharedKey"])
  197. }
  198. if peers[0]["publicKey"] != "pub-wg" {
  199. t.Fatalf("WireGuard peer publicKey = %v, want pub-wg", peers[0]["publicKey"])
  200. }
  201. if peers[0]["keepAlive"] != float64(15) {
  202. t.Fatalf("WireGuard peer keepAlive = %v, want 15", peers[0]["keepAlive"])
  203. }
  204. }
  205. func TestGetXrayConfigWireGuardOmitsKeepAliveAbsentFromSettings(t *testing.T) {
  206. email := seedDualTunnelClient(t, true, nil)
  207. var shared model.ClientRecord
  208. if err := database.GetDB().Where("email = ?", email).First(&shared).Error; err != nil {
  209. t.Fatalf("read shared client: %v", err)
  210. }
  211. if shared.PublicKey != "pub-awg" || shared.KeepAlive != 25 {
  212. t.Fatalf("test setup did not persist AmneziaWG identity: publicKey=%q keepAlive=%d", shared.PublicKey, shared.KeepAlive)
  213. }
  214. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
  215. if len(peers) != 1 {
  216. t.Fatalf("expected 1 peer, got %d: %v", len(peers), peers)
  217. }
  218. if peers[0]["publicKey"] != "pub-wg" {
  219. t.Fatalf("WireGuard peer publicKey = %v, want pub-wg", peers[0]["publicKey"])
  220. }
  221. if _, ok := peers[0]["keepAlive"]; ok {
  222. t.Fatalf("WireGuard peer keepAlive = %v, want absent", peers[0]["keepAlive"])
  223. }
  224. }
  225. func TestGetXrayConfigWireGuardDisabledDualProtocolClientExcluded(t *testing.T) {
  226. seedDualTunnelClient(t, false, model.KeepAlivePtr(15))
  227. peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual"))
  228. if len(peers) != 0 {
  229. t.Fatalf("expected disabled dual-protocol client to be excluded, got %v", peers)
  230. }
  231. }
  232. func TestGetXrayConfigWireGuardNoClientsEmitsEmptyPeers(t *testing.T) {
  233. seedWGInbound(t, "wg-empty", 51822, nil)
  234. settings := wgInboundEmittedSettings(t, "wg-empty")
  235. if _, ok := settings["clients"]; ok {
  236. t.Fatalf("clients key must be absent")
  237. }
  238. peers, ok := settings["peers"].([]any)
  239. if !ok {
  240. t.Fatalf("peers must be an (empty) array, got %T", settings["peers"])
  241. }
  242. if len(peers) != 0 {
  243. t.Fatalf("expected empty peers, got %v", peers)
  244. }
  245. }