xray_config_inject_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package service
  2. import (
  3. "encoding/json"
  4. "os"
  5. "testing"
  6. xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
  7. "github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
  8. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  9. "github.com/op/go-logging"
  10. )
  11. func TestMain(m *testing.M) {
  12. // injectPanelEgress logs when it skips injection; the package logger must
  13. // exist before any test exercises a skipped path.
  14. xuilogger.InitLogger(logging.ERROR)
  15. os.Exit(m.Run())
  16. }
  17. func TestEnsureAPIServices(t *testing.T) {
  18. // legacy template without RoutingService gets it injected
  19. out := ensureAPIServices(json_util.RawMessage(`{"services":["HandlerService","LoggerService","StatsService"],"tag":"api"}`))
  20. var parsed struct {
  21. Services []string `json:"services"`
  22. Tag string `json:"tag"`
  23. }
  24. if err := json.Unmarshal(out, &parsed); err != nil {
  25. t.Fatal(err)
  26. }
  27. want := map[string]bool{"HandlerService": true, "StatsService": true, "RoutingService": true, "LoggerService": true}
  28. if len(parsed.Services) != 4 {
  29. t.Fatalf("expected 4 services, got %v", parsed.Services)
  30. }
  31. for _, svc := range parsed.Services {
  32. if !want[svc] {
  33. t.Fatalf("unexpected service %q", svc)
  34. }
  35. }
  36. if parsed.Tag != "api" {
  37. t.Fatalf("tag must be preserved, got %q", parsed.Tag)
  38. }
  39. // complete api block is returned unchanged (no marshal churn)
  40. full := json_util.RawMessage(`{"services":["HandlerService","StatsService","RoutingService"],"tag":"api"}`)
  41. if got := ensureAPIServices(full); string(got) != string(full) {
  42. t.Fatalf("complete api block must pass through untouched, got %s", got)
  43. }
  44. // absent api block stays absent
  45. if got := ensureAPIServices(nil); got != nil {
  46. t.Fatalf("nil api block must stay nil, got %s", got)
  47. }
  48. }
  49. func egressTestConfig() *xray.Config {
  50. return &xray.Config{
  51. RouterConfig: json_util.RawMessage(`{"domainStrategy":"AsIs","rules":[{"type":"field","inboundTag":["api"],"outboundTag":"api"}]}`),
  52. InboundConfigs: []xray.InboundConfig{
  53. {Port: 62789, Protocol: "tunnel", Tag: "api", Listen: json_util.RawMessage(`"127.0.0.1"`)},
  54. },
  55. }
  56. }
  57. type egressRouting struct {
  58. DomainStrategy string `json:"domainStrategy"`
  59. Rules []struct {
  60. InboundTag []string `json:"inboundTag"`
  61. OutboundTag string `json:"outboundTag"`
  62. Type string `json:"type"`
  63. } `json:"rules"`
  64. }
  65. func TestInjectPanelEgress(t *testing.T) {
  66. cfg := egressTestConfig()
  67. injectPanelEgress(cfg, "warp")
  68. if len(cfg.InboundConfigs) != 2 {
  69. t.Fatalf("expected the egress inbound to be appended, got %d inbounds", len(cfg.InboundConfigs))
  70. }
  71. ib := cfg.InboundConfigs[1]
  72. if ib.Tag != PanelEgressInboundTag || ib.Protocol != "socks" || ib.Port != panelEgressBasePort {
  73. t.Fatalf("unexpected egress inbound: %+v", ib)
  74. }
  75. if string(ib.Listen) != `"127.0.0.1"` {
  76. t.Fatalf("egress inbound must listen on loopback, got %s", ib.Listen)
  77. }
  78. var routing egressRouting
  79. if err := json.Unmarshal(cfg.RouterConfig, &routing); err != nil {
  80. t.Fatal(err)
  81. }
  82. if routing.DomainStrategy != "AsIs" {
  83. t.Fatalf("routing keys outside rules must be preserved, got %+v", routing)
  84. }
  85. if len(routing.Rules) != 2 {
  86. t.Fatalf("expected egress rule + existing rule, got %+v", routing.Rules)
  87. }
  88. first := routing.Rules[0]
  89. if first.Type != "field" || first.OutboundTag != "warp" ||
  90. len(first.InboundTag) != 1 || first.InboundTag[0] != PanelEgressInboundTag {
  91. t.Fatalf("egress rule must be prepended, got %+v", first)
  92. }
  93. }
  94. func TestInjectPanelEgress_PortCollision(t *testing.T) {
  95. cfg := egressTestConfig()
  96. cfg.InboundConfigs = append(cfg.InboundConfigs,
  97. xray.InboundConfig{Port: panelEgressBasePort, Protocol: "vless", Tag: "in-1"},
  98. xray.InboundConfig{Port: panelEgressBasePort + 1, Protocol: "vless", Tag: "in-2"},
  99. )
  100. injectPanelEgress(cfg, "direct")
  101. got := cfg.InboundConfigs[len(cfg.InboundConfigs)-1]
  102. if got.Tag != PanelEgressInboundTag || got.Port != panelEgressBasePort+2 {
  103. t.Fatalf("egress inbound must skip taken ports, got %+v", got)
  104. }
  105. }
  106. func TestInjectPanelEgress_TagCollisionSkips(t *testing.T) {
  107. cfg := egressTestConfig()
  108. cfg.InboundConfigs = append(cfg.InboundConfigs,
  109. xray.InboundConfig{Port: 1234, Protocol: "socks", Tag: PanelEgressInboundTag},
  110. )
  111. before := string(cfg.RouterConfig)
  112. injectPanelEgress(cfg, "direct")
  113. if len(cfg.InboundConfigs) != 2 || string(cfg.RouterConfig) != before {
  114. t.Fatal("a user inbound owning the egress tag must make injection a no-op")
  115. }
  116. }
  117. func TestInjectPanelEgress_NoRoutingSection(t *testing.T) {
  118. cfg := egressTestConfig()
  119. cfg.RouterConfig = nil
  120. injectPanelEgress(cfg, "direct")
  121. var routing egressRouting
  122. if err := json.Unmarshal(cfg.RouterConfig, &routing); err != nil {
  123. t.Fatal(err)
  124. }
  125. if len(routing.Rules) != 1 || routing.Rules[0].OutboundTag != "direct" {
  126. t.Fatalf("a routing section must be created with the egress rule, got %+v", routing)
  127. }
  128. if len(cfg.InboundConfigs) != 2 {
  129. t.Fatal("egress inbound must still be appended")
  130. }
  131. }
  132. func TestInjectPanelEgress_BadRoutingSkips(t *testing.T) {
  133. cfg := egressTestConfig()
  134. cfg.RouterConfig = json_util.RawMessage(`{not json`)
  135. injectPanelEgress(cfg, "direct")
  136. if len(cfg.InboundConfigs) != 1 {
  137. t.Fatal("unparsable routing must skip the whole injection, inbound included")
  138. }
  139. if string(cfg.RouterConfig) != `{not json` {
  140. t.Fatal("unparsable routing must be left untouched")
  141. }
  142. }