1
0

wireguard_domain_strategy_migration_test.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package database
  2. import (
  3. "encoding/json"
  4. "strings"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/config"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  9. )
  10. const wgTestKeys = `"secretKey":"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=","peers":[{"publicKey":"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=","endpoint":"engage.cloudflareclient.com:2408"}]`
  11. func TestRewriteWireguardDomainStrategy(t *testing.T) {
  12. tests := []struct {
  13. name string
  14. outbound string
  15. wantChanged bool
  16. wantRoot string
  17. wantSockopt string
  18. wantDNS bool
  19. }{
  20. {
  21. name: "the WARP default moves to both places the core now reads",
  22. outbound: `{"protocol":"wireguard","tag":"warp","settings":{"domainStrategy":"ForceIPv4v6",` + wgTestKeys + `}}`,
  23. wantChanged: true, wantRoot: "ForceIPv4v6", wantSockopt: "ForceIPv4v6",
  24. },
  25. {
  26. name: "values the admin already set win over the legacy key",
  27. outbound: `{"protocol":"wireguard","tag":"wg","targetStrategy":"UseIPv6","streamSettings":{"sockopt":{"domainStrategy":"UseIPv4"}},"settings":{"domainStrategy":"forceipv6",` + wgTestKeys + `}}`,
  28. wantChanged: true, wantRoot: "UseIPv6", wantSockopt: "UseIPv4",
  29. },
  30. {
  31. name: "plain ForceIP had no family preference, so only the key goes",
  32. outbound: `{"protocol":"wireguard","tag":"wg","settings":{"domainStrategy":"ForceIP",` + wgTestKeys + `}}`,
  33. wantChanged: true,
  34. },
  35. {
  36. name: "remoteDNS local becomes targetStrategy, which resolves with the built-in DNS",
  37. outbound: `{"protocol":"wireguard","tag":"wg","settings":{"domainStrategy":"ForceIPv4","remoteDNS":["local"],` + wgTestKeys + `}}`,
  38. wantChanged: true, wantRoot: "ForceIPv4", wantSockopt: "ForceIPv4",
  39. },
  40. {
  41. name: "remoteDNS local without a strategy resolves any family",
  42. outbound: `{"protocol":"wireguard","tag":"wg","settings":{"remoteDNS":["local"],` + wgTestKeys + `}}`,
  43. wantChanged: true, wantRoot: "ForceIP",
  44. },
  45. {
  46. name: "a strategy the old core refused is dropped rather than moved",
  47. outbound: `{"protocol":"wireguard","tag":"wg","settings":{"domainStrategy":"UseIPv4",` + wgTestKeys + `}}`,
  48. wantChanged: true,
  49. },
  50. {
  51. name: "IP remoteDNS entries stay",
  52. outbound: `{"protocol":"wireguard","tag":"wg","settings":{"remoteDNS":["1.1.1.1"],` + wgTestKeys + `}}`,
  53. wantChanged: false, wantDNS: true,
  54. },
  55. }
  56. for _, tc := range tests {
  57. t.Run(tc.name, func(t *testing.T) {
  58. updated, changed, err := rewriteWireguardDomainStrategy(`{"outbounds":[` + tc.outbound + `]}`)
  59. if err != nil {
  60. t.Fatalf("rewrite: %v", err)
  61. }
  62. if changed != tc.wantChanged {
  63. t.Fatalf("changed = %v, want %v", changed, tc.wantChanged)
  64. }
  65. var cfg struct {
  66. Outbounds []json.RawMessage `json:"outbounds"`
  67. }
  68. if err := json.Unmarshal([]byte(updated), &cfg); err != nil || len(cfg.Outbounds) != 1 {
  69. t.Fatalf("rewritten template unreadable (%v): %s", err, updated)
  70. }
  71. var got struct {
  72. TargetStrategy string `json:"targetStrategy"`
  73. StreamSettings struct {
  74. Sockopt struct {
  75. DomainStrategy string `json:"domainStrategy"`
  76. } `json:"sockopt"`
  77. } `json:"streamSettings"`
  78. Settings map[string]any `json:"settings"`
  79. }
  80. if err := json.Unmarshal(cfg.Outbounds[0], &got); err != nil {
  81. t.Fatal(err)
  82. }
  83. if got.TargetStrategy != tc.wantRoot {
  84. t.Errorf("targetStrategy = %q, want %q", got.TargetStrategy, tc.wantRoot)
  85. }
  86. if got.StreamSettings.Sockopt.DomainStrategy != tc.wantSockopt {
  87. t.Errorf("sockopt.domainStrategy = %q, want %q", got.StreamSettings.Sockopt.DomainStrategy, tc.wantSockopt)
  88. }
  89. if _, kept := got.Settings["domainStrategy"]; kept {
  90. t.Errorf("settings.domainStrategy survived the rewrite: %s", cfg.Outbounds[0])
  91. }
  92. if _, kept := got.Settings["remoteDNS"]; kept != tc.wantDNS {
  93. t.Errorf("remoteDNS kept = %v, want %v", kept, tc.wantDNS)
  94. }
  95. if err := xray.ValidateOutboundConfig(cfg.Outbounds[0]); err != nil {
  96. t.Fatalf("xray-core refuses the rewritten outbound: %v", err)
  97. }
  98. })
  99. }
  100. }
  101. func TestWireguardDomainStrategySeederRewritesStoredTemplateOnce(t *testing.T) {
  102. t.Setenv("XUI_DB_FOLDER", t.TempDir())
  103. if err := InitDB(config.GetDBPath()); err != nil {
  104. if strings.Contains(err.Error(), "CGO_ENABLED=0") {
  105. t.Skipf("sqlite needs cgo: %v", err)
  106. }
  107. t.Fatalf("init db: %v", err)
  108. }
  109. t.Cleanup(func() { _ = CloseDB() })
  110. legacy := `{"outbounds":[{"protocol":"wireguard","tag":"warp","settings":{"domainStrategy":"ForceIPv4v6",` + wgTestKeys + `}}]}`
  111. seedTemplate(t, legacy)
  112. if err := db.Where("seeder_name = ?", "WireguardDomainStrategyFix").
  113. Delete(&model.HistoryOfSeeders{}).Error; err != nil {
  114. t.Fatalf("clear seeder history: %v", err)
  115. }
  116. if err := runSeeders(false); err != nil {
  117. t.Fatalf("runSeeders: %v", err)
  118. }
  119. var cfg struct {
  120. Outbounds []struct {
  121. TargetStrategy string `json:"targetStrategy"`
  122. Settings map[string]any `json:"settings"`
  123. } `json:"outbounds"`
  124. }
  125. if err := json.Unmarshal([]byte(storedTemplate(t)), &cfg); err != nil || len(cfg.Outbounds) != 1 {
  126. t.Fatalf("stored template unreadable (%v)", err)
  127. }
  128. if _, kept := cfg.Outbounds[0].Settings["domainStrategy"]; kept || cfg.Outbounds[0].TargetStrategy != "ForceIPv4v6" {
  129. t.Fatalf("stored outbound was not rewritten: %+v", cfg.Outbounds[0])
  130. }
  131. // The history gate keeps a hand-edited template from being rewritten on every restart.
  132. seedTemplate(t, legacy)
  133. if err := runSeeders(false); err != nil {
  134. t.Fatalf("runSeeders: %v", err)
  135. }
  136. if got := storedTemplate(t); got != legacy {
  137. t.Errorf("a completed seeder rewrote the template again: %s", got)
  138. }
  139. }