1
0

inbound_mtproto_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package service
  2. import (
  3. "encoding/hex"
  4. "encoding/json"
  5. "strings"
  6. "testing"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. )
  9. func TestMtprotoRoutesThroughXray(t *testing.T) {
  10. cases := map[string]struct {
  11. ib *model.Inbound
  12. want bool
  13. }{
  14. "routed": {&model.Inbound{Protocol: model.MTProto, Settings: `{"routeThroughXray":true}`}, true},
  15. "off": {&model.Inbound{Protocol: model.MTProto, Settings: `{"routeThroughXray":false}`}, false},
  16. "absent": {&model.Inbound{Protocol: model.MTProto, Settings: `{}`}, false},
  17. "non-mtproto": {&model.Inbound{Protocol: model.VLESS, Settings: `{"routeThroughXray":true}`}, false},
  18. "bad json": {&model.Inbound{Protocol: model.MTProto, Settings: `{nope`}, false},
  19. "nil": {nil, false},
  20. }
  21. for name, c := range cases {
  22. if got := mtprotoRoutesThroughXray(c.ib); got != c.want {
  23. t.Fatalf("%s: got %v want %v", name, got, c.want)
  24. }
  25. }
  26. }
  27. func routeXrayPortOf(t *testing.T, settings string) int {
  28. t.Helper()
  29. var parsed map[string]any
  30. if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
  31. t.Fatalf("settings not valid JSON: %v\n%s", err, settings)
  32. }
  33. return settingsRouteXrayPort(parsed)
  34. }
  35. func TestNormalizeMtprotoXrayPort(t *testing.T) {
  36. s := &InboundService{}
  37. // Non-mtproto inbounds are left alone.
  38. ib := &model.Inbound{Protocol: model.VLESS, Settings: `{"x":1}`}
  39. if err := s.normalizeMtprotoXrayPort(ib, ""); err != nil {
  40. t.Fatal(err)
  41. }
  42. if ib.Settings != `{"x":1}` {
  43. t.Fatalf("non-mtproto settings must be untouched, got %s", ib.Settings)
  44. }
  45. // Routing on with no existing port allocates a fresh one.
  46. ib = &model.Inbound{Protocol: model.MTProto, Settings: `{"routeThroughXray":true}`}
  47. if err := s.normalizeMtprotoXrayPort(ib, ""); err != nil {
  48. t.Fatal(err)
  49. }
  50. if p := routeXrayPortOf(t, ib.Settings); p <= 0 {
  51. t.Fatalf("a routed inbound must get a port, got %d", p)
  52. }
  53. // On update, the stored port wins over both a missing and a client-echoed
  54. // value — the backend owns it, so no churn and no client override.
  55. ib = &model.Inbound{Protocol: model.MTProto, Settings: `{"routeThroughXray":true,"routeXrayPort":99999}`}
  56. if err := s.normalizeMtprotoXrayPort(ib, `{"routeThroughXray":true,"routeXrayPort":51000}`); err != nil {
  57. t.Fatal(err)
  58. }
  59. if p := routeXrayPortOf(t, ib.Settings); p != 51000 {
  60. t.Fatalf("stored port must win, got %d", p)
  61. }
  62. // An already-present port (no old settings) is stable and not re-marshaled.
  63. const stable = `{"routeThroughXray":true,"routeXrayPort":52000}`
  64. ib = &model.Inbound{Protocol: model.MTProto, Settings: stable}
  65. if err := s.normalizeMtprotoXrayPort(ib, ""); err != nil {
  66. t.Fatal(err)
  67. }
  68. if ib.Settings != stable {
  69. t.Fatalf("stable settings must pass through untouched, got %s", ib.Settings)
  70. }
  71. // Turning routing off strips both the bridge port and the inert outbound.
  72. ib = &model.Inbound{Protocol: model.MTProto, Settings: `{"routeThroughXray":false,"routeXrayPort":53000,"outboundTag":"warp"}`}
  73. if err := s.normalizeMtprotoXrayPort(ib, ""); err != nil {
  74. t.Fatal(err)
  75. }
  76. if p := routeXrayPortOf(t, ib.Settings); p != 0 {
  77. t.Fatalf("disabling routing must drop the port, got %d", p)
  78. }
  79. var parsed map[string]any
  80. if err := json.Unmarshal([]byte(ib.Settings), &parsed); err != nil {
  81. t.Fatal(err)
  82. }
  83. if _, ok := parsed["outboundTag"]; ok {
  84. t.Fatalf("disabling routing must drop the inert outbound tag, got %s", ib.Settings)
  85. }
  86. }
  87. func TestFillProtocolDefaultsMtproto(t *testing.T) {
  88. cs := &ClientService{}
  89. ib := &model.Inbound{Protocol: model.MTProto, Settings: `{"fakeTlsDomain":"example.com"}`}
  90. c := &model.Client{Email: "u"}
  91. if err := cs.fillProtocolDefaults(c, ib); err != nil {
  92. t.Fatal(err)
  93. }
  94. if !strings.HasPrefix(c.Secret, "ee") || !strings.HasSuffix(c.Secret, hex.EncodeToString([]byte("example.com"))) {
  95. t.Fatalf("mtproto client should get a FakeTLS secret fronting the inbound domain, got %q", c.Secret)
  96. }
  97. // An existing secret is not overwritten.
  98. pre := &model.Client{Email: "v", Secret: "eepreset"}
  99. if err := cs.fillProtocolDefaults(pre, ib); err != nil {
  100. t.Fatal(err)
  101. }
  102. if pre.Secret != "eepreset" {
  103. t.Fatalf("an existing secret must be preserved, got %q", pre.Secret)
  104. }
  105. // With no inbound domain the default fronting host is used.
  106. c2 := &model.Client{Email: "w"}
  107. if err := cs.fillProtocolDefaults(c2, &model.Inbound{Protocol: model.MTProto, Settings: `{}`}); err != nil {
  108. t.Fatal(err)
  109. }
  110. if !strings.HasSuffix(c2.Secret, hex.EncodeToString([]byte(defaultMtprotoDomain))) {
  111. t.Fatalf("a domainless inbound should front the default host, got %q", c2.Secret)
  112. }
  113. }
  114. func TestNormalizeMtprotoSecretHealsClients(t *testing.T) {
  115. s := &InboundService{}
  116. ib := &model.Inbound{Protocol: model.MTProto, Settings: `{"fakeTlsDomain":"a.com","clients":[{"email":"x","secret":""}]}`}
  117. s.normalizeMtprotoSecret(ib)
  118. var parsed map[string]any
  119. if err := json.Unmarshal([]byte(ib.Settings), &parsed); err != nil {
  120. t.Fatalf("healed settings not valid json: %v", err)
  121. }
  122. clients := parsed["clients"].([]any)
  123. got := clients[0].(map[string]any)["secret"].(string)
  124. if !strings.HasPrefix(got, "ee") || !strings.HasSuffix(got, hex.EncodeToString([]byte("a.com"))) {
  125. t.Fatalf("client secret should be healed to front the inbound domain, got %q", got)
  126. }
  127. }