Release 3.8.2.11: idle rev-cache, port-safe Connect, single-instance tray UX.
ci / test (macos-latest) (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run

Faster unchanged polls; orphan-core cleanup on listen ports; clearer busy-port errors; cores only from binDir; incremental server list; CSP without Google Fonts; single-instance activates existing window; close-to-tray without kill-others or balloon.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-08-01 17:27:47 +03:00
co-authored by Cursor
parent fed1ace33e
commit 57d719f02f
45 changed files with 876 additions and 285 deletions
+66
View File
@@ -29,9 +29,12 @@ type Result struct {
}
// Target describes one profile to ping.
// Prefer Host (host or host:port) so large proxy/AWG bodies are not copied;
// Proxy is used only when Host is empty.
type Target struct {
Name string
Protocol config.Protocol
Host string
Proxy string
}
@@ -70,6 +73,10 @@ func PingAll(ctx context.Context, targets []Target) []Result {
defer wg.Done()
for i := range jobs {
t := targets[i]
if hp := strings.TrimSpace(t.Host); hp != "" {
out[i] = PingHost(ctx, t.Name, t.Protocol, hp)
continue
}
if strings.TrimSpace(t.Proxy) == "" {
out[i] = Result{Name: t.Name, Error: "нет ссылки сервера"}
continue
@@ -114,6 +121,65 @@ func BestOK(results []Result) (Result, bool) {
return best, found
}
// PingHost probes a pre-parsed host or host:port (no proxy URI / secrets).
// Naive/Xray use TCP; Hysteria 2 / AWG use UDP.
func PingHost(ctx context.Context, name string, proto config.Protocol, hostPort string) Result {
res := Result{Name: name}
hostPort = strings.TrimSpace(hostPort)
if hostPort == "" {
res.Error = "нет хоста"
return res
}
host, port := splitHostPort(hostPort, "443")
res.Host = host
res.Port = port
if host == "" {
res.Error = "нет хоста"
return res
}
udp := proto == config.ProtocolHysteria2 || proto == config.ProtocolAWG
var (
rtt time.Duration
soft bool
err error
)
if udp {
rtt, soft, err = probeUDP(ctx, host, port)
} else {
rtt, err = probeTCP(ctx, host, port)
}
if err != nil {
res.Error = friendlyDialError(err, udp)
return res
}
ms := rtt.Milliseconds()
if ms < 1 {
ms = 1
}
res.Ms = ms
res.OK = true
res.Soft = soft
return res
}
func splitHostPort(hostPort, defaultPort string) (host, port string) {
hostPort = strings.TrimSpace(hostPort)
if hostPort == "" {
return "", defaultPort
}
if h, p, err := net.SplitHostPort(hostPort); err == nil {
return h, p
}
// bare IPv6 without port
if strings.Count(hostPort, ":") >= 2 && !strings.HasPrefix(hostPort, "[") {
return hostPort, defaultPort
}
if i := strings.LastIndex(hostPort, ":"); i >= 0 && !strings.Contains(hostPort[i+1:], "]") {
return hostPort[:i], hostPort[i+1:]
}
return hostPort, defaultPort
}
// PingProfile pings using protocol hints when available.
// Naive uses TCP; Hysteria 2 / AWG use UDP — TCP would always look "refused".
// When proto is set, Detect is skipped (trust stored protocol).
+24
View File
@@ -32,6 +32,30 @@ func TestPingProfileEmpty(t *testing.T) {
}
}
func TestSplitHostPort(t *testing.T) {
h, p := splitHostPort("example.com:8443", "443")
if h != "example.com" || p != "8443" {
t.Fatalf("%s %s", h, p)
}
h, p = splitHostPort("example.com", "443")
if h != "example.com" || p != "443" {
t.Fatalf("%s %s", h, p)
}
h, p = splitHostPort("[2001:db8::1]:443", "80")
if h != "2001:db8::1" || p != "443" {
t.Fatalf("%s %s", h, p)
}
}
func TestPingHostEmpty(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r := PingHost(ctx, "x", config.ProtocolNaive, "")
if r.OK || r.Error == "" {
t.Fatalf("%+v", r)
}
}
func TestProbeUDPSoftOKWhenSilent(t *testing.T) {
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {