Release 3.8.2.1: reliability, UI probe/logs, CI, signing gates.
Clear sysproxy on core death; probe+reconnect; subscription prune; Best ignores soft UDP; Windows tray; Android protocol gate; CI workflows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+17
-15
@@ -24,6 +24,7 @@ type Result struct {
|
||||
Port string `json:"port"`
|
||||
Ms int64 `json:"ms"`
|
||||
OK bool `json:"ok"`
|
||||
Soft bool `json:"soft,omitempty"` // UDP soft-up (timeout, no ICMP refuse)
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@@ -90,12 +91,13 @@ func PingAll(ctx context.Context, targets []Target) []Result {
|
||||
return sorted
|
||||
}
|
||||
|
||||
// BestOK returns the lowest-latency successful result, if any.
|
||||
// BestOK returns the lowest-latency hard OK result (excludes UDP soft-up).
|
||||
// Soft-up nodes stay visible in the list but are not chosen as «Лучший».
|
||||
func BestOK(results []Result) (Result, bool) {
|
||||
var best Result
|
||||
found := false
|
||||
for _, r := range results {
|
||||
if !r.OK {
|
||||
if !r.OK || r.Soft {
|
||||
continue
|
||||
}
|
||||
if !found || r.Ms < best.Ms {
|
||||
@@ -186,11 +188,12 @@ func PingProfile(ctx context.Context, name string, proto config.Protocol, proxyU
|
||||
}
|
||||
|
||||
var (
|
||||
rtt time.Duration
|
||||
err error
|
||||
rtt time.Duration
|
||||
soft bool
|
||||
err error
|
||||
)
|
||||
if hy2 || isAWG {
|
||||
rtt, err = probeUDP(ctx, host, port)
|
||||
rtt, soft, err = probeUDP(ctx, host, port)
|
||||
} else {
|
||||
rtt, err = probeTCP(ctx, host, port)
|
||||
}
|
||||
@@ -204,6 +207,7 @@ func PingProfile(ctx context.Context, name string, proto config.Protocol, proxyU
|
||||
}
|
||||
res.Ms = ms
|
||||
res.OK = true
|
||||
res.Soft = soft
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -223,18 +227,17 @@ func probeTCP(ctx context.Context, host, port string) (time.Duration, error) {
|
||||
// UDP VPN ports usually ignore a probe datagram (no reply). Treating read-timeout
|
||||
// as failure marked live nodes as down. Semantics:
|
||||
// - ICMP / connection refused → down
|
||||
// - any reply → up (RTT to reply)
|
||||
// - short read-timeout after a successful write → soft-up (RTT ≈ DNS+dial+write)
|
||||
func probeUDP(ctx context.Context, host, port string) (time.Duration, error) {
|
||||
// - any reply → hard up (RTT to reply)
|
||||
// - short read-timeout after a successful write → soft-up (display only; not for Best)
|
||||
func probeUDP(ctx context.Context, host, port string) (time.Duration, bool, error) {
|
||||
start := time.Now()
|
||||
d := net.Dialer{Timeout: 3 * time.Second}
|
||||
conn, err := d.DialContext(ctx, "udp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Brief window only to catch ICMP port-unreachable; do not wait for an app reply.
|
||||
icmpWait := 350 * time.Millisecond
|
||||
deadline := time.Now().Add(icmpWait)
|
||||
if dl, ok := ctx.Deadline(); ok && dl.Before(deadline) {
|
||||
@@ -243,20 +246,19 @@ func probeUDP(ctx context.Context, host, port string) (time.Duration, error) {
|
||||
_ = conn.SetDeadline(deadline)
|
||||
|
||||
if _, err := conn.Write([]byte{0}); err != nil {
|
||||
return 0, err
|
||||
return 0, false, err
|
||||
}
|
||||
afterWrite := time.Now()
|
||||
|
||||
buf := make([]byte, 64)
|
||||
_, err = conn.Read(buf)
|
||||
if err == nil {
|
||||
return time.Since(start), nil
|
||||
return time.Since(start), false, nil
|
||||
}
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
// No ICMP refuse → endpoint is plausible; latency without the wait pad.
|
||||
return afterWrite.Sub(start), nil
|
||||
return afterWrite.Sub(start), true, nil
|
||||
}
|
||||
return 0, err
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
func friendlyDialError(err error, udp bool) string {
|
||||
|
||||
Reference in New Issue
Block a user