Skip to content

Commit fc17422

Browse files
Add files via upload
1 parent a512b22 commit fc17422

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

main.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
}

sys_other.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build !windows
2+
3+
package main
4+
5+
import (
6+
"os/exec"
7+
)
8+
9+
func setHideWindow(cmd *exec.Cmd) {
10+
// No-op on Linux and macOS, as there's no console window to hide
11+
}

sys_windows.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build windows
2+
3+
package main
4+
5+
import (
6+
"os/exec"
7+
"syscall"
8+
)
9+
10+
func setHideWindow(cmd *exec.Cmd) {
11+
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
12+
}

0 commit comments

Comments
 (0)