subJsonService_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package sub
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/database/model"
  6. )
  7. func hasDirectOutOutbound(svc *SubJsonService) bool {
  8. for _, raw := range svc.defaultOutbounds {
  9. var outbound map[string]any
  10. if err := json.Unmarshal(raw, &outbound); err != nil {
  11. continue
  12. }
  13. if outbound["tag"] == "direct_out" {
  14. return true
  15. }
  16. }
  17. return false
  18. }
  19. func outboundSettings(t *testing.T, raw []byte) map[string]any {
  20. t.Helper()
  21. var parsed map[string]any
  22. if err := json.Unmarshal(raw, &parsed); err != nil {
  23. t.Fatalf("failed to unmarshal outbound: %v", err)
  24. }
  25. settings, _ := parsed["settings"].(map[string]any)
  26. if settings == nil {
  27. t.Fatal("outbound has no settings")
  28. }
  29. return settings
  30. }
  31. func TestSubJsonServiceInjectsGlobalFinalMask(t *testing.T) {
  32. finalMask := `{"tcp":[{"type":"fragment","settings":{"packets":"tlshello","length":"100-200","delay":"10-20"}}],"udp":[{"type":"noise","settings":{"noise":[{"type":"base64","packet":"SGVsbG8="}]}}],"quicParams":{"congestion":"bbr"}}`
  33. svc := NewSubJsonService("", "", finalMask, nil)
  34. if hasDirectOutOutbound(svc) {
  35. t.Fatal("direct_out outbound must never be emitted")
  36. }
  37. stream := svc.streamData(`{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"none"}}}`)
  38. if _, ok := stream["sockopt"]; ok {
  39. t.Fatal("legacy direct_out dialerProxy sockopt must never be set")
  40. }
  41. finalmask, _ := stream["finalmask"].(map[string]any)
  42. if finalmask == nil {
  43. t.Fatal("streamSettings is missing finalmask")
  44. }
  45. tcp, _ := finalmask["tcp"].([]any)
  46. if len(tcp) != 1 {
  47. t.Fatalf("tcp masks len = %d, want 1", len(tcp))
  48. }
  49. if first, _ := tcp[0].(map[string]any); first["type"] != "fragment" {
  50. t.Fatalf("tcp[0] type = %v, want fragment", first["type"])
  51. }
  52. udp, _ := finalmask["udp"].([]any)
  53. if len(udp) != 1 {
  54. t.Fatalf("udp masks len = %d, want 1", len(udp))
  55. }
  56. quic, _ := finalmask["quicParams"].(map[string]any)
  57. if quic == nil || quic["congestion"] != "bbr" {
  58. t.Fatalf("quicParams missing/wrong: %#v", finalmask["quicParams"])
  59. }
  60. }
  61. func TestSubJsonServiceMergesWithExistingFinalMask(t *testing.T) {
  62. finalMask := `{"tcp":[{"type":"fragment","settings":{"packets":"tlshello"}}]}`
  63. svc := NewSubJsonService("", "", finalMask, nil)
  64. stream := svc.streamData(`{
  65. "network":"tcp","security":"none","tcpSettings":{"header":{"type":"none"}},
  66. "finalmask":{"tcp":[{"type":"sudoku"}]}
  67. }`)
  68. finalmask, _ := stream["finalmask"].(map[string]any)
  69. tcp, _ := finalmask["tcp"].([]any)
  70. if len(tcp) != 2 {
  71. t.Fatalf("tcp masks len = %d, want 2 (existing + global)", len(tcp))
  72. }
  73. a, _ := tcp[0].(map[string]any)
  74. b, _ := tcp[1].(map[string]any)
  75. if a["type"] != "sudoku" || b["type"] != "fragment" {
  76. t.Fatalf("tcp masks = %#v, want existing sudoku then global fragment", tcp)
  77. }
  78. }
  79. func TestSubJsonServiceNoFinalMaskWhenEmpty(t *testing.T) {
  80. svc := NewSubJsonService("", "", "", nil)
  81. stream := svc.streamData(`{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"none"}}}`)
  82. if _, ok := stream["finalmask"]; ok {
  83. t.Fatal("no finalmask should be emitted when subJsonFinalMask is empty")
  84. }
  85. if _, ok := stream["sockopt"]; ok {
  86. t.Fatal("legacy direct_out sockopt must never be set")
  87. }
  88. }
  89. func TestSubJsonServiceVlessFlattened(t *testing.T) {
  90. inbound := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.VLESS, Settings: `{"encryption":"none"}`}
  91. client := model.Client{ID: "uuid-1", Flow: "xtls-rprx-vision"}
  92. settings := outboundSettings(t, NewSubJsonService("", "", "", nil).genVless(inbound, nil, client))
  93. if _, ok := settings["vnext"]; ok {
  94. t.Fatal("vless outbound must not use vnext")
  95. }
  96. if settings["address"] != "1.2.3.4" || settings["id"] != "uuid-1" || settings["encryption"] != "none" || settings["flow"] != "xtls-rprx-vision" {
  97. t.Fatalf("flat vless settings wrong: %#v", settings)
  98. }
  99. }
  100. func TestSubJsonServiceVmessFlattened(t *testing.T) {
  101. inbound := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.VMESS, Settings: `{}`}
  102. client := model.Client{ID: "uuid-2"}
  103. settings := outboundSettings(t, NewSubJsonService("", "", "", nil).genVnext(inbound, nil, client))
  104. if _, ok := settings["vnext"]; ok {
  105. t.Fatal("vmess outbound must not use vnext")
  106. }
  107. if settings["id"] != "uuid-2" || settings["security"] != "auto" {
  108. t.Fatalf("flat vmess settings wrong: %#v", settings)
  109. }
  110. }
  111. func TestSubJsonServiceServerFlattened(t *testing.T) {
  112. trojan := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.Trojan, Settings: `{}`}
  113. client := model.Client{Password: "p4ss"}
  114. settings := outboundSettings(t, NewSubJsonService("", "", "", nil).genServer(trojan, nil, client))
  115. if _, ok := settings["servers"]; ok {
  116. t.Fatal("trojan outbound must not use servers array")
  117. }
  118. if settings["password"] != "p4ss" || settings["address"] != "1.2.3.4" {
  119. t.Fatalf("flat trojan settings wrong: %#v", settings)
  120. }
  121. ss := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.Shadowsocks, Settings: `{"method":"aes-256-gcm"}`}
  122. ssSettings := outboundSettings(t, NewSubJsonService("", "", "", nil).genServer(ss, nil, client))
  123. if ssSettings["method"] != "aes-256-gcm" {
  124. t.Fatalf("flat shadowsocks must carry method: %#v", ssSettings)
  125. }
  126. }