probe_protocol_case_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package outbound
  2. import (
  3. "encoding/json"
  4. "net"
  5. "net/http"
  6. "net/http/httptest"
  7. "net/url"
  8. "testing"
  9. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  10. )
  11. // The core lowercases a protocol id and a transport name before it resolves
  12. // either, so every reader here has to accept the spelling the core accepts.
  13. func TestTestOutboundsTCPModeForcesCoreSpelledUDPToHTTPProbe(t *testing.T) {
  14. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  15. w.WriteHeader(http.StatusNoContent)
  16. }))
  17. defer srv.Close()
  18. withStubProcess(t, func(cfg *xray.Config, configPath string) batchProcess {
  19. return &stubProcess{cfg: cfg, serveSocks: true}
  20. })
  21. withEgressTraceProbe(t, func(*url.URL) *TestEgressResult {
  22. return &TestEgressResult{IPv4: "198.51.100.2", Country: "ZZ", Warp: "off"}
  23. })
  24. batch := mustJSON(t, []any{map[string]any{"tag": "wg", "protocol": "WireGuard"}})
  25. results, err := (&OutboundService{}).TestOutbounds(batch, srv.URL, "", "tcp")
  26. if err != nil {
  27. t.Fatalf("TestOutbounds: %v", err)
  28. }
  29. r := results[0]
  30. if !r.Success || r.Mode != "http" {
  31. t.Errorf(`"WireGuard" outbound in tcp mode = %+v, want success with mode %q`, r, "http")
  32. }
  33. if r.Egress == nil || r.Egress.IPv4 != "198.51.100.2" {
  34. t.Errorf(`"WireGuard" outbound egress = %+v`, r.Egress)
  35. }
  36. }
  37. func TestOutboundTransportIsUDPMatchesTheCore(t *testing.T) {
  38. tests := []struct {
  39. name string
  40. ob map[string]any
  41. want bool
  42. }{
  43. {"canonical wireguard", map[string]any{"protocol": "wireguard"}, true},
  44. {"capitalised wireguard", map[string]any{"protocol": "WireGuard"}, true},
  45. {"upper hysteria", map[string]any{"protocol": "HYSTERIA"}, true},
  46. {"amneziawg", map[string]any{"protocol": "amneziawg"}, true},
  47. {"kcp transport", map[string]any{"streamSettings": map[string]any{"network": "kcp"}}, true},
  48. {"kcp transport capitalised", map[string]any{"streamSettings": map[string]any{"network": "KCP"}}, true},
  49. {"mkcp alias", map[string]any{"streamSettings": map[string]any{"network": "mkcp"}}, true},
  50. {"mkcp alias capitalised", map[string]any{"streamSettings": map[string]any{"network": "MKCP"}}, true},
  51. {"tcp transport", map[string]any{"streamSettings": map[string]any{"network": "tcp"}}, false},
  52. {"plain vless", map[string]any{"protocol": "vless"}, false},
  53. {"matched but tcp", map[string]any{"protocol": "vless", "streamSettings": map[string]any{"network": "ws"}}, false},
  54. }
  55. for _, tt := range tests {
  56. t.Run(tt.name, func(t *testing.T) {
  57. if got := outboundTransportIsUDP(tt.ob); got != tt.want {
  58. t.Errorf("outboundTransportIsUDP(%v) = %v, want %v", tt.ob, got, tt.want)
  59. }
  60. })
  61. }
  62. }
  63. func TestBuildBatchTestConfigReadsTheProtocolIDLikeTheCore(t *testing.T) {
  64. items := []*httpBatchItem{
  65. {tag: "wg", outbound: map[string]any{"tag": "wg", "protocol": "WireGuard"}},
  66. {tag: "awg", outbound: map[string]any{"tag": "awg", "protocol": "AmneziaWG"}},
  67. }
  68. cfg := buildBatchTestConfig(items, nil, []int{61011, 61012})
  69. raw, err := json.Marshal(cfg)
  70. if err != nil {
  71. t.Fatalf("marshal config: %v", err)
  72. }
  73. var m map[string]any
  74. if err := json.Unmarshal(raw, &m); err != nil {
  75. t.Fatalf("unmarshal config: %v", err)
  76. }
  77. outbounds, _ := m["outbounds"].([]any)
  78. byTag := make(map[string]map[string]any, len(outbounds))
  79. for _, entry := range outbounds {
  80. ob, _ := entry.(map[string]any)
  81. tag, _ := ob["tag"].(string)
  82. byTag[tag] = ob
  83. }
  84. wg := byTag["wg"]
  85. if wg == nil {
  86. t.Fatalf("wg outbound missing from the temp config: %v", outbounds)
  87. }
  88. if settings, _ := wg["settings"].(map[string]any); settings == nil || settings["noKernelTun"] != true {
  89. t.Errorf(`"WireGuard" settings = %v, want noKernelTun: the probe instance must not create a kernel device`, wg["settings"])
  90. }
  91. awg := byTag["awg"]
  92. if awg == nil {
  93. t.Fatalf("awg outbound missing from the temp config: %v", outbounds)
  94. }
  95. if protocol, _ := awg["protocol"].(string); protocol != "socks" {
  96. t.Errorf(`"AmneziaWG" protocol = %q, want %q: a raw amneziawg entry fails the whole temp config`, protocol, "socks")
  97. }
  98. }
  99. func TestTestOutboundsTCPLaneReadsProtocolIDCaseInsensitively(t *testing.T) {
  100. l, err := net.Listen("tcp", "127.0.0.1:0")
  101. if err != nil {
  102. t.Fatalf("listen: %v", err)
  103. }
  104. defer l.Close()
  105. go func() {
  106. for {
  107. conn, err := l.Accept()
  108. if err != nil {
  109. return
  110. }
  111. conn.Close()
  112. }
  113. }()
  114. port := l.Addr().(*net.TCPAddr).Port
  115. batch := mustJSON(t, []any{map[string]any{
  116. "tag": "t1",
  117. "protocol": "SOCKS",
  118. "settings": map[string]any{"servers": []any{map[string]any{"address": "127.0.0.1", "port": port}}},
  119. }})
  120. results, err := (&OutboundService{}).TestOutbounds(batch, "", "", "tcp")
  121. if err != nil {
  122. t.Fatalf("TestOutbounds: %v", err)
  123. }
  124. r := results[0]
  125. if !r.Success || r.Mode != "tcp" || len(r.Endpoints) != 1 {
  126. t.Errorf(`"SOCKS" outbound in tcp mode = %+v, want a successful tcp probe with one endpoint`, r)
  127. }
  128. }