| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- package outbound
- import (
- "encoding/json"
- "net"
- "net/http"
- "net/http/httptest"
- "net/url"
- "testing"
- "github.com/mhsanaei/3x-ui/v3/internal/xray"
- )
- // The core lowercases a protocol id and a transport name before it resolves
- // either, so every reader here has to accept the spelling the core accepts.
- func TestTestOutboundsTCPModeForcesCoreSpelledUDPToHTTPProbe(t *testing.T) {
- srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNoContent)
- }))
- defer srv.Close()
- withStubProcess(t, func(cfg *xray.Config, configPath string) batchProcess {
- return &stubProcess{cfg: cfg, serveSocks: true}
- })
- withEgressTraceProbe(t, func(*url.URL) *TestEgressResult {
- return &TestEgressResult{IPv4: "198.51.100.2", Country: "ZZ", Warp: "off"}
- })
- batch := mustJSON(t, []any{map[string]any{"tag": "wg", "protocol": "WireGuard"}})
- results, err := (&OutboundService{}).TestOutbounds(batch, srv.URL, "", "tcp")
- if err != nil {
- t.Fatalf("TestOutbounds: %v", err)
- }
- r := results[0]
- if !r.Success || r.Mode != "http" {
- t.Errorf(`"WireGuard" outbound in tcp mode = %+v, want success with mode %q`, r, "http")
- }
- if r.Egress == nil || r.Egress.IPv4 != "198.51.100.2" {
- t.Errorf(`"WireGuard" outbound egress = %+v`, r.Egress)
- }
- }
- func TestOutboundTransportIsUDPMatchesTheCore(t *testing.T) {
- tests := []struct {
- name string
- ob map[string]any
- want bool
- }{
- {"canonical wireguard", map[string]any{"protocol": "wireguard"}, true},
- {"capitalised wireguard", map[string]any{"protocol": "WireGuard"}, true},
- {"upper hysteria", map[string]any{"protocol": "HYSTERIA"}, true},
- {"amneziawg", map[string]any{"protocol": "amneziawg"}, true},
- {"kcp transport", map[string]any{"streamSettings": map[string]any{"network": "kcp"}}, true},
- {"kcp transport capitalised", map[string]any{"streamSettings": map[string]any{"network": "KCP"}}, true},
- {"mkcp alias", map[string]any{"streamSettings": map[string]any{"network": "mkcp"}}, true},
- {"mkcp alias capitalised", map[string]any{"streamSettings": map[string]any{"network": "MKCP"}}, true},
- {"tcp transport", map[string]any{"streamSettings": map[string]any{"network": "tcp"}}, false},
- {"plain vless", map[string]any{"protocol": "vless"}, false},
- {"matched but tcp", map[string]any{"protocol": "vless", "streamSettings": map[string]any{"network": "ws"}}, false},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := outboundTransportIsUDP(tt.ob); got != tt.want {
- t.Errorf("outboundTransportIsUDP(%v) = %v, want %v", tt.ob, got, tt.want)
- }
- })
- }
- }
- func TestBuildBatchTestConfigReadsTheProtocolIDLikeTheCore(t *testing.T) {
- items := []*httpBatchItem{
- {tag: "wg", outbound: map[string]any{"tag": "wg", "protocol": "WireGuard"}},
- {tag: "awg", outbound: map[string]any{"tag": "awg", "protocol": "AmneziaWG"}},
- }
- cfg := buildBatchTestConfig(items, nil, []int{61011, 61012})
- raw, err := json.Marshal(cfg)
- if err != nil {
- t.Fatalf("marshal config: %v", err)
- }
- var m map[string]any
- if err := json.Unmarshal(raw, &m); err != nil {
- t.Fatalf("unmarshal config: %v", err)
- }
- outbounds, _ := m["outbounds"].([]any)
- byTag := make(map[string]map[string]any, len(outbounds))
- for _, entry := range outbounds {
- ob, _ := entry.(map[string]any)
- tag, _ := ob["tag"].(string)
- byTag[tag] = ob
- }
- wg := byTag["wg"]
- if wg == nil {
- t.Fatalf("wg outbound missing from the temp config: %v", outbounds)
- }
- if settings, _ := wg["settings"].(map[string]any); settings == nil || settings["noKernelTun"] != true {
- t.Errorf(`"WireGuard" settings = %v, want noKernelTun: the probe instance must not create a kernel device`, wg["settings"])
- }
- awg := byTag["awg"]
- if awg == nil {
- t.Fatalf("awg outbound missing from the temp config: %v", outbounds)
- }
- if protocol, _ := awg["protocol"].(string); protocol != "socks" {
- t.Errorf(`"AmneziaWG" protocol = %q, want %q: a raw amneziawg entry fails the whole temp config`, protocol, "socks")
- }
- }
- func TestTestOutboundsRejectsUntestableIDsInAnyCase(t *testing.T) {
- tests := []struct {
- name string
- protocol string
- }{
- {"canonical freedom", "freedom"},
- {"capitalised freedom", "Freedom"},
- {"upper freedom", "FREEDOM"},
- {"canonical blackhole", "blackhole"},
- {"capitalised blackhole", "Blackhole"},
- }
- const wantErr = "Outbound has no testable endpoint"
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- batch := mustJSON(t, []any{map[string]any{"tag": "t1", "protocol": tt.protocol}})
- results, err := (&OutboundService{}).TestOutbounds(batch, "", "", "tcp")
- if err != nil {
- t.Fatalf("TestOutbounds: %v", err)
- }
- r := results[0]
- if r.Success {
- t.Errorf("%q outbound = %+v, want a rejection", tt.protocol, r)
- }
- if r.Error != wantErr {
- t.Errorf("%q error = %q, want %q", tt.protocol, r.Error, wantErr)
- }
- })
- }
- }
- func TestTestOutboundsTCPLaneReadsProtocolIDCaseInsensitively(t *testing.T) {
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("listen: %v", err)
- }
- defer l.Close()
- go func() {
- for {
- conn, err := l.Accept()
- if err != nil {
- return
- }
- conn.Close()
- }
- }()
- port := l.Addr().(*net.TCPAddr).Port
- batch := mustJSON(t, []any{map[string]any{
- "tag": "t1",
- "protocol": "SOCKS",
- "settings": map[string]any{"servers": []any{map[string]any{"address": "127.0.0.1", "port": port}}},
- }})
- results, err := (&OutboundService{}).TestOutbounds(batch, "", "", "tcp")
- if err != nil {
- t.Fatalf("TestOutbounds: %v", err)
- }
- r := results[0]
- if !r.Success || r.Mode != "tcp" || len(r.Endpoints) != 1 {
- t.Errorf(`"SOCKS" outbound in tcp mode = %+v, want a successful tcp probe with one endpoint`, r)
- }
- }
|