finalmask_rand_packet_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package service
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. func streamWithNoiseItem(t *testing.T, item map[string]any) map[string]any {
  8. t.Helper()
  9. return map[string]any{
  10. "network": "tcp",
  11. "finalmask": map[string]any{
  12. "udp": []any{map[string]any{
  13. "type": "noise",
  14. "settings": map[string]any{
  15. "reset": "60",
  16. "noise": []any{item},
  17. },
  18. }},
  19. },
  20. }
  21. }
  22. func noiseItem(t *testing.T, stream map[string]any) map[string]any {
  23. t.Helper()
  24. finalmask, _ := stream["finalmask"].(map[string]any)
  25. udp, _ := finalmask["udp"].([]any)
  26. mask, _ := udp[0].(map[string]any)
  27. settings, _ := mask["settings"].(map[string]any)
  28. noise, _ := settings["noise"].([]any)
  29. item, _ := noise[0].(map[string]any)
  30. return item
  31. }
  32. // TestDropEmptyRandPacketsClearsEditorResidue is the regression for the mask
  33. // editor writing packet:[] alongside a rand. xray-core counts the empty array
  34. // as a packet and refuses the config, which keeps every inbound offline.
  35. func TestDropEmptyRandPacketsClearsEditorResidue(t *testing.T) {
  36. stream := streamWithNoiseItem(t, map[string]any{
  37. "type": "array",
  38. "rand": "1-8192",
  39. "packet": []any{},
  40. "delay": "5",
  41. })
  42. if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 1 {
  43. t.Fatalf("cleared = %d, want 1", cleared)
  44. }
  45. item := noiseItem(t, stream)
  46. if _, present := item["packet"]; present {
  47. t.Fatalf("packet survived: %#v", item)
  48. }
  49. if item["rand"] != "1-8192" || item["delay"] != "5" {
  50. t.Fatalf("healing changed the mask: %#v", item)
  51. }
  52. }
  53. func TestDropEmptyRandPacketsLeavesRealPacketsAlone(t *testing.T) {
  54. tests := []struct {
  55. name string
  56. item map[string]any
  57. }{
  58. {"packet without a rand", map[string]any{"type": "array", "packet": []any{1.0, 2.0}, "rand": 0.0}},
  59. {"empty packet without a rand", map[string]any{"type": "array", "packet": []any{}}},
  60. {"empty packet with a zero rand", map[string]any{"type": "array", "packet": []any{}, "rand": 0.0}},
  61. {"empty packet with a zero range", map[string]any{"type": "array", "packet": []any{}, "rand": "0-0"}},
  62. {"string packet", map[string]any{"type": "str", "packet": "ping", "rand": "1-10"}},
  63. }
  64. for _, tt := range tests {
  65. t.Run(tt.name, func(t *testing.T) {
  66. stream := streamWithNoiseItem(t, tt.item)
  67. if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 0 {
  68. t.Fatalf("cleared = %d, want the item left alone", cleared)
  69. }
  70. if _, present := noiseItem(t, stream)["packet"]; !present {
  71. t.Fatal("packet was dropped")
  72. }
  73. })
  74. }
  75. }
  76. // TestDropEmptyRandPacketsReachesNestedItems covers header-custom, whose items
  77. // sit two arrays deep and are subject to the same exclusive-kind rule.
  78. func TestDropEmptyRandPacketsReachesNestedItems(t *testing.T) {
  79. stream := map[string]any{
  80. "finalmask": map[string]any{
  81. "tcp": []any{map[string]any{
  82. "type": "header-custom",
  83. "settings": map[string]any{
  84. "clients": []any{[]any{map[string]any{"type": "array", "rand": 64.0, "packet": []any{}}}},
  85. "servers": []any{[]any{map[string]any{"type": "array", "rand": 32.0, "packet": []any{}}}},
  86. },
  87. }},
  88. },
  89. }
  90. if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 2 {
  91. t.Fatalf("cleared = %d, want 2", cleared)
  92. }
  93. }
  94. func TestDropEmptyRandPacketsIgnoresMissingFinalMask(t *testing.T) {
  95. stream := map[string]any{"network": "tcp"}
  96. if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 0 {
  97. t.Fatalf("cleared = %d, want 0", cleared)
  98. }
  99. }
  100. // TestHealedConfigsBuildInXray closes the loop on both heals: the rows xray
  101. // refuses outright must build once the panel has healed them.
  102. func TestHealedConfigsBuildInXray(t *testing.T) {
  103. t.Run("hysteria v1 row", func(t *testing.T) {
  104. in := model.Inbound{
  105. Protocol: model.Hysteria,
  106. Port: 36715,
  107. Listen: "127.0.0.1",
  108. Tag: "in-hysteria",
  109. Settings: `{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`,
  110. StreamSettings: `{"network":"hysteria","hysteriaSettings":{"version":1,"udpIdleTimeout":60}}`,
  111. }
  112. raw, err := json.Marshal(in.GenXrayInboundConfig())
  113. if err != nil {
  114. t.Fatalf("marshal generated inbound: %v", err)
  115. }
  116. var healed map[string]any
  117. if err := json.Unmarshal(raw, &healed); err != nil {
  118. t.Fatalf("decode generated inbound: %v", err)
  119. }
  120. assertXrayAccepts(t, "the healed hysteria inbound", buildGoldenInbound(t, healed))
  121. var unhealed map[string]any
  122. if err := json.Unmarshal([]byte(`{
  123. "tag":"in-hysteria","listen":"127.0.0.1","port":36715,"protocol":"hysteria",
  124. "settings":`+in.Settings+`,"streamSettings":`+in.StreamSettings+`}`), &unhealed); err != nil {
  125. t.Fatalf("decode raw inbound: %v", err)
  126. }
  127. if err := buildGoldenInbound(t, unhealed); err == nil {
  128. t.Fatal("the unhealed v1 row is expected to be refused; the heal is what makes it buildable")
  129. }
  130. })
  131. t.Run("noise item with an empty packet", func(t *testing.T) {
  132. item := map[string]any{"type": "array", "rand": "1-8192", "packet": []any{}, "delay": "5"}
  133. stream := streamWithNoiseItem(t, item)
  134. inbound := map[string]any{
  135. "tag": "in-vless", "listen": "127.0.0.1", "port": 8443, "protocol": "vless",
  136. "settings": map[string]any{"clients": []any{}, "decryption": "none"},
  137. "streamSettings": stream,
  138. }
  139. if err := buildGoldenInbound(t, inbound); err == nil {
  140. t.Fatal("xray-core is expected to refuse a packet and a rand on one item")
  141. }
  142. dropEmptyRandPackets(stream["finalmask"])
  143. inbound["streamSettings"] = stream
  144. assertXrayAccepts(t, "the healed noise mask", buildGoldenInbound(t, inbound))
  145. })
  146. }