1+ package main
2+
3+ import (
4+ "bytes"
5+ "context"
6+ "encoding/json"
7+ "fmt"
8+ "io"
9+ "net"
10+ "net/http"
11+ "os"
12+ "os/exec"
13+ "runtime"
14+ "strings"
15+ "sync"
16+ "time"
17+
18+ "golang.org/x/net/proxy"
19+ )
20+
21+ type TestConfig struct {
22+ Tag string `json:"tag"`
23+ Protocol string `json:"protocol"`
24+ Config json.RawMessage `json:"config"`
25+ TestPort int `json:"test_port"`
26+ ClientPath string `json:"client_path"`
27+ FragmentConfig json.RawMessage `json:"fragment_config,omitempty"`
28+ }
29+
30+ type TestResult struct {
31+ Tag string `json:"tag"`
32+ Ping int64 `json:"ping_ms"`
33+ Status string `json:"status"`
34+ }
35+
36+ func main () {
37+ inputData , err := io .ReadAll (os .Stdin )
38+ if err != nil { os .Exit (1 ) }
39+ var configs []TestConfig
40+ if err := json .Unmarshal (inputData , & configs ); err != nil { os .Exit (1 ) }
41+
42+ results := make (chan TestResult , len (configs ))
43+ var wg sync.WaitGroup
44+
45+ for _ , conf := range configs {
46+ wg .Add (1 )
47+ go func (c TestConfig ) {
48+ defer wg .Done ()
49+
50+ var cmd * exec.Cmd
51+ var configFile * os.File
52+
53+ ctx , cancel := context .WithTimeout (context .Background (), 15 * time .Second )
54+ defer cancel ()
55+
56+ if c .Protocol == "hysteria" || c .Protocol == "hysteria2" {
57+ configFile , err = os .CreateTemp ("" , "hysteria-*.json" )
58+ if err != nil { results <- TestResult {Tag : c .Tag , Ping : - 1 , Status : "tempfile_error" }; return }
59+ defer os .Remove (configFile .Name ())
60+ configFile .Write (c .Config )
61+ configFile .Close ()
62+ cmd = exec .CommandContext (ctx , c .ClientPath , "client" , "-c" , configFile .Name ())
63+
64+ } else {
65+ configFile , err = os .CreateTemp ("" , "xray-*.json" )
66+ if err != nil { results <- TestResult {Tag : c .Tag , Ping : - 1 , Status : "tempfile_error" }; return }
67+ defer os .Remove (configFile .Name ())
68+
69+ outbounds := []json.RawMessage {c .Config }
70+ if len (c .FragmentConfig ) > 0 && string (c .FragmentConfig ) != "null" {
71+ fragmentOutbound := map [string ]interface {}{ "protocol" : "freedom" , "tag" : "fragment" , "settings" : map [string ]json.RawMessage {"fragment" : c .FragmentConfig }}
72+ fragmentBytes , _ := json .Marshal (fragmentOutbound )
73+ outbounds = append (outbounds , json .RawMessage (fragmentBytes ))
74+ }
75+
76+ fullConfig := map [string ]interface {}{
77+ "log" : map [string ]string {"loglevel" : "warning" },
78+ "inbounds" : []map [string ]interface {}{{"protocol" : "socks" , "port" : c .TestPort , "listen" : "127.0.0.1" , "settings" : map [string ]interface {}{"auth" : "noauth" , "udp" : true }}},
79+ "outbounds" : outbounds ,
80+ }
81+ configBytes , _ := json .Marshal (fullConfig )
82+ configFile .Write (configBytes )
83+ configFile .Close ()
84+
85+ cmd = exec .CommandContext (ctx , c .ClientPath , "-c" , configFile .Name ())
86+ }
87+
88+
89+ if runtime .GOOS == "windows" {
90+ setHideWindow (cmd )
91+ }
92+
93+ var clientOutput bytes.Buffer
94+ cmd .Stdout = & clientOutput
95+ cmd .Stderr = & clientOutput
96+
97+ if err := cmd .Start (); err != nil {
98+ results <- TestResult {Tag : c .Tag , Ping : - 1 , Status : "client_start_failed" }; return
99+ }
100+
101+ time .Sleep (900 * time .Millisecond )
102+ ping , status := testProxy (c .TestPort )
103+
104+ if status != "success" {
105+ logStr := strings .ReplaceAll (string (clientOutput .Bytes ()), "\n " , " " )
106+ if len (logStr ) > 200 { logStr = logStr [:200 ] }
107+ status = fmt .Sprintf ("%s | log: %s" , status , logStr )
108+ }
109+
110+ cmd .Process .Kill ()
111+ cmd .Wait ()
112+
113+ results <- TestResult {Tag : c .Tag , Ping : ping , Status : status }
114+ }(conf )
115+ }
116+
117+ wg .Wait ()
118+ close (results )
119+
120+ finalResults := make ([]TestResult , 0 , len (configs ))
121+ for res := range results { finalResults = append (finalResults , res ) }
122+ outputData , _ := json .Marshal (finalResults )
123+ fmt .Println (string (outputData ))
124+ }
125+
126+ func testProxy (port int ) (int64 , string ) {
127+ targetURL := "http://www.google.com/generate_204"
128+ timeout := 8 * time .Second
129+ dialer , err := proxy .SOCKS5 ("tcp" , fmt .Sprintf ("127.0.0.1:%d" , port ), nil , proxy .Direct )
130+ if err != nil { return - 1 , fmt .Sprintf ("failed_dialer: %v" , err ) }
131+ httpClient := & http.Client { Transport : & http.Transport {DialContext : func (ctx context.Context , network , addr string ) (net.Conn , error ) { return dialer .Dial (network , addr ) }}, Timeout : timeout }
132+ start := time .Now ()
133+ resp , err := httpClient .Get (targetURL )
134+ if err != nil { return - 1 , fmt .Sprintf ("failed_http: %v" , err ) }
135+ defer resp .Body .Close ()
136+ if resp .StatusCode != http .StatusNoContent { return - 1 , fmt .Sprintf ("bad_status_%d" , resp .StatusCode ) }
137+ return time .Since (start ).Milliseconds (), "success"
138+ }
0 commit comments